• 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
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