• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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