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 { 16 AndroidLogConfig, 17 AndroidLogId, 18 AndroidPowerConfig, 19 BufferConfig, 20 DataSourceConfig, 21 FtraceConfig, 22 ProcessStatsConfig, 23 SysStatsConfig, 24 TraceConfig 25} from '../common/protos'; 26import {MeminfoCounters, VmstatCounters} from '../common/protos'; 27import {RecordConfig} from '../common/state'; 28 29import {Controller} from './controller'; 30import {App} from './globals'; 31 32export function uint8ArrayToBase64(buffer: Uint8Array): string { 33 return btoa(String.fromCharCode.apply(null, Array.from(buffer))); 34} 35 36export function genConfigProto(uiCfg: RecordConfig): Uint8Array { 37 const protoCfg = new TraceConfig(); 38 protoCfg.durationMs = uiCfg.durationMs; 39 40 // Auxiliary buffer for slow-rate events. 41 // Set to 1/8th of the main buffer size, with reasonable limits. 42 let slowBufSizeKb = uiCfg.bufferSizeMb * (1024 / 8); 43 slowBufSizeKb = Math.min(slowBufSizeKb, 2 * 1024); 44 slowBufSizeKb = Math.max(slowBufSizeKb, 256); 45 46 // Main buffer for ftrace and other high-freq events. 47 const fastBufSizeKb = uiCfg.bufferSizeMb * 1024 - slowBufSizeKb; 48 49 protoCfg.buffers.push(new BufferConfig()); 50 protoCfg.buffers.push(new BufferConfig()); 51 protoCfg.buffers[1].sizeKb = slowBufSizeKb; 52 protoCfg.buffers[0].sizeKb = fastBufSizeKb; 53 54 if (uiCfg.mode === 'STOP_WHEN_FULL') { 55 protoCfg.buffers[0].fillPolicy = BufferConfig.FillPolicy.DISCARD; 56 protoCfg.buffers[1].fillPolicy = BufferConfig.FillPolicy.DISCARD; 57 } else { 58 protoCfg.buffers[0].fillPolicy = BufferConfig.FillPolicy.RING_BUFFER; 59 protoCfg.buffers[1].fillPolicy = BufferConfig.FillPolicy.RING_BUFFER; 60 protoCfg.flushPeriodMs = 30000; 61 if (uiCfg.mode === 'LONG_TRACE') { 62 protoCfg.writeIntoFile = true; 63 protoCfg.fileWritePeriodMs = uiCfg.fileWritePeriodMs; 64 protoCfg.maxFileSizeBytes = uiCfg.maxFileSizeMb * 1e6; 65 } 66 } 67 68 const ftraceEvents = new Set<string>(uiCfg.ftrace ? uiCfg.ftraceEvents : []); 69 const atraceCats = new Set<string>(uiCfg.atrace ? uiCfg.atraceCats : []); 70 const atraceApps = new Set<string>(); 71 let procThreadAssociationPolling = false; 72 let procThreadAssociationFtrace = false; 73 let trackInitialOomScore = false; 74 75 if (uiCfg.cpuSched || uiCfg.cpuLatency) { 76 procThreadAssociationPolling = true; 77 procThreadAssociationFtrace = true; 78 ftraceEvents.add('sched/sched_switch'); 79 ftraceEvents.add('power/suspend_resume'); 80 if (uiCfg.cpuLatency) { 81 ftraceEvents.add('sched/sched_wakeup'); 82 ftraceEvents.add('sched/sched_wakeup_new'); 83 ftraceEvents.add('power/suspend_resume'); 84 } 85 } 86 87 if (uiCfg.cpuFreq) { 88 ftraceEvents.add('power/cpu_frequency'); 89 ftraceEvents.add('power/cpu_idle'); 90 ftraceEvents.add('power/suspend_resume'); 91 } 92 93 if (procThreadAssociationFtrace) { 94 ftraceEvents.add('sched/sched_process_exit'); 95 ftraceEvents.add('sched/sched_process_free'); 96 ftraceEvents.add('task/task_newtask'); 97 ftraceEvents.add('task/task_rename'); 98 } 99 100 if (uiCfg.batteryDrain) { 101 const ds = new TraceConfig.DataSource(); 102 ds.config = new DataSourceConfig(); 103 ds.config.name = 'android.power'; 104 ds.config.androidPowerConfig = new AndroidPowerConfig(); 105 ds.config.androidPowerConfig.batteryPollMs = uiCfg.batteryDrainPollMs; 106 ds.config.androidPowerConfig.batteryCounters = [ 107 AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CAPACITY_PERCENT, 108 AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CHARGE, 109 AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CURRENT, 110 ]; 111 ds.config.androidPowerConfig.collectPowerRails = true; 112 protoCfg.dataSources.push(ds); 113 } 114 115 if (uiCfg.boardSensors) { 116 ftraceEvents.add('regulator/regulator_set_voltage'); 117 ftraceEvents.add('regulator/regulator_set_voltage_complete'); 118 ftraceEvents.add('power/clock_enable'); 119 ftraceEvents.add('power/clock_disable'); 120 ftraceEvents.add('power/clock_set_rate'); 121 ftraceEvents.add('power/suspend_resume'); 122 } 123 124 let sysStatsCfg: SysStatsConfig|undefined = undefined; 125 126 if (uiCfg.cpuCoarse) { 127 if (sysStatsCfg === undefined) sysStatsCfg = new SysStatsConfig(); 128 sysStatsCfg.statPeriodMs = uiCfg.cpuCoarsePollMs; 129 sysStatsCfg.statCounters = [ 130 SysStatsConfig.StatCounters.STAT_CPU_TIMES, 131 SysStatsConfig.StatCounters.STAT_FORK_COUNT, 132 ]; 133 } 134 135 if (uiCfg.memHiFreq) { 136 procThreadAssociationPolling = true; 137 procThreadAssociationFtrace = true; 138 ftraceEvents.add('kmem/rss_stat'); 139 ftraceEvents.add('kmem/mm_event'); 140 ftraceEvents.add('kmem/ion_heap_grow'); 141 ftraceEvents.add('kmem/ion_heap_shrink'); 142 } 143 144 if (uiCfg.meminfo) { 145 if (sysStatsCfg === undefined) sysStatsCfg = new SysStatsConfig(); 146 sysStatsCfg.meminfoPeriodMs = uiCfg.meminfoPeriodMs; 147 sysStatsCfg.meminfoCounters = uiCfg.meminfoCounters.map(name => { 148 // tslint:disable-next-line no-any 149 return MeminfoCounters[name as any as number] as any as number; 150 }); 151 } 152 153 if (uiCfg.vmstat) { 154 if (sysStatsCfg === undefined) sysStatsCfg = new SysStatsConfig(); 155 sysStatsCfg.vmstatPeriodMs = uiCfg.vmstatPeriodMs; 156 sysStatsCfg.vmstatCounters = uiCfg.vmstatCounters.map(name => { 157 // tslint:disable-next-line no-any 158 return VmstatCounters[name as any as number] as any as number; 159 }); 160 } 161 162 if (uiCfg.memLmk) { 163 // For in-kernel LMK (roughly older devices until Go and Pixel 3). 164 ftraceEvents.add('lowmemorykiller/lowmemory_kill'); 165 166 // For userspace LMKd (newer devices). 167 // 'lmkd' is not really required because the code in lmkd.c emits events 168 // with ATRACE_TAG_ALWAYS. We need something just to ensure that the final 169 // config will enable atrace userspace events. 170 atraceApps.add('lmkd'); 171 172 ftraceEvents.add('oom/oom_score_adj_update'); 173 procThreadAssociationPolling = true; 174 trackInitialOomScore = true; 175 } 176 177 if (uiCfg.procStats || procThreadAssociationPolling || trackInitialOomScore) { 178 const ds = new TraceConfig.DataSource(); 179 ds.config = new DataSourceConfig(); 180 ds.config.targetBuffer = 1; // Aux 181 ds.config.name = 'linux.process_stats'; 182 ds.config.processStatsConfig = new ProcessStatsConfig(); 183 if (uiCfg.procStats) { 184 ds.config.processStatsConfig.procStatsPollMs = uiCfg.procStatsPeriodMs; 185 } 186 if (procThreadAssociationPolling || trackInitialOomScore) { 187 ds.config.processStatsConfig.scanAllProcessesOnStart = true; 188 } 189 protoCfg.dataSources.push(ds); 190 } 191 192 if (uiCfg.androidLogs) { 193 const ds = new TraceConfig.DataSource(); 194 ds.config = new DataSourceConfig(); 195 ds.config.name = 'android.log'; 196 ds.config.androidLogConfig = new AndroidLogConfig(); 197 ds.config.androidLogConfig.logIds = uiCfg.androidLogBuffers.map(name => { 198 // tslint:disable-next-line no-any 199 return AndroidLogId[name as any as number] as any as number; 200 }); 201 202 protoCfg.dataSources.push(ds); 203 } 204 205 // Keep these last. The stages above can enrich them. 206 207 if (sysStatsCfg !== undefined) { 208 const ds = new TraceConfig.DataSource(); 209 ds.config = new DataSourceConfig(); 210 ds.config.name = 'linux.sys_stats'; 211 ds.config.sysStatsConfig = sysStatsCfg; 212 protoCfg.dataSources.push(ds); 213 } 214 215 if (uiCfg.ftrace || uiCfg.atraceApps.length > 0 || ftraceEvents.size > 0 || 216 atraceCats.size > 0 || atraceApps.size > 0) { 217 const ds = new TraceConfig.DataSource(); 218 ds.config = new DataSourceConfig(); 219 ds.config.name = 'linux.ftrace'; 220 ds.config.ftraceConfig = new FtraceConfig(); 221 // Override the advanced ftrace parameters only if the user has ticked the 222 // "Advanced ftrace config" tab. 223 if (uiCfg.ftrace) { 224 ds.config.ftraceConfig.bufferSizeKb = uiCfg.ftraceBufferSizeKb; 225 ds.config.ftraceConfig.drainPeriodMs = uiCfg.ftraceDrainPeriodMs; 226 for (const line of uiCfg.ftraceExtraEvents.split('\n')) { 227 if (line.trim().length > 0) ftraceEvents.add(line.trim()); 228 } 229 } 230 for (const line of uiCfg.atraceApps.split('\n')) { 231 if (line.trim().length > 0) atraceApps.add(line.trim()); 232 } 233 234 if (atraceCats.size > 0 || atraceApps.size > 0) { 235 ftraceEvents.add('ftrace/print'); 236 } 237 238 ds.config.ftraceConfig.ftraceEvents = Array.from(ftraceEvents); 239 ds.config.ftraceConfig.atraceCategories = Array.from(atraceCats); 240 ds.config.ftraceConfig.atraceApps = Array.from(atraceApps); 241 protoCfg.dataSources.push(ds); 242 } 243 244 const buffer = TraceConfig.encode(protoCfg).finish(); 245 return buffer; 246} 247 248export function toPbtxt(configBuffer: Uint8Array): string { 249 const msg = TraceConfig.decode(configBuffer); 250 const json = msg.toJSON(); 251 function snakeCase(s: string): string { 252 return s.replace(/[A-Z]/g, c => '_' + c.toLowerCase()); 253 } 254 // With the ahead of time compiled protos we can't seem to tell which 255 // fields are enums. 256 function looksLikeEnum(value: string): boolean { 257 return value.startsWith('MEMINFO_') || value.startsWith('VMSTAT_') || 258 value.startsWith('STAT_') || value.startsWith('LID_') || 259 value.startsWith('BATTERY_COUNTER_') || value === 'DISCARD' || 260 value === 'RING_BUFFER'; 261 } 262 function* message(msg: {}, indent: number): IterableIterator<string> { 263 for (const [key, value] of Object.entries(msg)) { 264 const isRepeated = Array.isArray(value); 265 const isNested = typeof value === 'object' && !isRepeated; 266 for (const entry of (isRepeated ? value as Array<{}> : [value])) { 267 yield ' '.repeat(indent) + `${snakeCase(key)}${isNested ? '' : ':'} `; 268 if (typeof entry === 'string') { 269 yield looksLikeEnum(entry) ? entry : `"${entry}"`; 270 } else if (typeof entry === 'number') { 271 yield entry.toString(); 272 } else if (typeof entry === 'boolean') { 273 yield entry.toString(); 274 } else { 275 yield '{\n'; 276 yield* message(entry, indent + 4); 277 yield ' '.repeat(indent) + '}'; 278 } 279 yield '\n'; 280 } 281 } 282 } 283 return [...message(json, 0)].join(''); 284} 285 286export class RecordController extends Controller<'main'> { 287 private app: App; 288 private config: RecordConfig|null = null; 289 290 constructor(args: {app: App}) { 291 super('main'); 292 this.app = args.app; 293 } 294 295 run() { 296 if (this.app.state.recordConfig === this.config) return; 297 this.config = this.app.state.recordConfig; 298 const configProto = genConfigProto(this.config); 299 const configProtoText = toPbtxt(configProto); 300 const commandline = ` 301 echo '${uint8ArrayToBase64(configProto)}' | 302 base64 --decode | 303 adb shell "perfetto -c - -o /data/misc/perfetto-traces/trace" && 304 adb pull /data/misc/perfetto-traces/trace /tmp/trace 305 `; 306 // TODO(hjd): This should not be TrackData after we unify the stores. 307 this.app.publish('TrackData', { 308 id: 'config', 309 data: { 310 commandline, 311 pbtxt: configProtoText, 312 } 313 }); 314 } 315} 316