• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
15
16import {produce} from 'immer';
17import * as m from 'mithril';
18
19import {Actions} from '../common/actions';
20import {MeminfoCounters, VmstatCounters} from '../common/protos';
21import {
22  AdbRecordingTarget,
23  getBuiltinChromeCategoryList,
24  getDefaultRecordingTargets,
25  hasActiveProbes,
26  isAdbTarget,
27  isAndroidP,
28  isAndroidTarget,
29  isChromeTarget,
30  isCrOSTarget,
31  RecordingTarget
32} from '../common/state';
33import {MAX_TIME, RecordMode} from '../common/state';
34import {AdbOverWebUsb} from '../controller/adb';
35
36import {globals} from './globals';
37import {createPage} from './pages';
38import {recordConfigStore} from './record_config';
39import {
40  CodeSnippet,
41  Dropdown,
42  DropdownAttrs,
43  Probe,
44  ProbeAttrs,
45  Slider,
46  SliderAttrs,
47  Textarea,
48  TextareaAttrs,
49  Toggle,
50  ToggleAttrs
51} from './record_widgets';
52import {Router} from './router';
53
54const LOCAL_STORAGE_SHOW_CONFIG = 'showConfigs';
55
56const POLL_INTERVAL_MS = [250, 500, 1000, 2500, 5000, 30000, 60000];
57
58const ATRACE_CATEGORIES = new Map<string, string>();
59ATRACE_CATEGORIES.set('adb', 'ADB');
60ATRACE_CATEGORIES.set('aidl', 'AIDL calls');
61ATRACE_CATEGORIES.set('am', 'Activity Manager');
62ATRACE_CATEGORIES.set('audio', 'Audio');
63ATRACE_CATEGORIES.set('binder_driver', 'Binder Kernel driver');
64ATRACE_CATEGORIES.set('binder_lock', 'Binder global lock trace');
65ATRACE_CATEGORIES.set('bionic', 'Bionic C library');
66ATRACE_CATEGORIES.set('camera', 'Camera');
67ATRACE_CATEGORIES.set('dalvik', 'ART & Dalvik');
68ATRACE_CATEGORIES.set('database', 'Database');
69ATRACE_CATEGORIES.set('gfx', 'Graphics');
70ATRACE_CATEGORIES.set('hal', 'Hardware Modules');
71ATRACE_CATEGORIES.set('input', 'Input');
72ATRACE_CATEGORIES.set('network', 'Network');
73ATRACE_CATEGORIES.set('nnapi', 'Neural Network API');
74ATRACE_CATEGORIES.set('pm', 'Package Manager');
75ATRACE_CATEGORIES.set('power', 'Power Management');
76ATRACE_CATEGORIES.set('res', 'Resource Loading');
77ATRACE_CATEGORIES.set('rro', 'Resource Overlay');
78ATRACE_CATEGORIES.set('rs', 'RenderScript');
79ATRACE_CATEGORIES.set('sm', 'Sync Manager');
80ATRACE_CATEGORIES.set('ss', 'System Server');
81ATRACE_CATEGORIES.set('vibrator', 'Vibrator');
82ATRACE_CATEGORIES.set('video', 'Video');
83ATRACE_CATEGORIES.set('view', 'View System');
84ATRACE_CATEGORIES.set('webview', 'WebView');
85ATRACE_CATEGORIES.set('wm', 'Window Manager');
86
87const LOG_BUFFERS = new Map<string, string>();
88LOG_BUFFERS.set('LID_CRASH', 'Crash');
89LOG_BUFFERS.set('LID_DEFAULT', 'Main');
90LOG_BUFFERS.set('LID_EVENTS', 'Binary events');
91LOG_BUFFERS.set('LID_KERNEL', 'Kernel');
92LOG_BUFFERS.set('LID_RADIO', 'Radio');
93LOG_BUFFERS.set('LID_SECURITY', 'Security');
94LOG_BUFFERS.set('LID_STATS', 'Stats');
95LOG_BUFFERS.set('LID_SYSTEM', 'System');
96
97const FTRACE_CATEGORIES = new Map<string, string>();
98FTRACE_CATEGORIES.set('binder/*', 'binder');
99FTRACE_CATEGORIES.set('block/*', 'block');
100FTRACE_CATEGORIES.set('clk/*', 'clk');
101FTRACE_CATEGORIES.set('ext4/*', 'ext4');
102FTRACE_CATEGORIES.set('f2fs/*', 'f2fs');
103FTRACE_CATEGORIES.set('i2c/*', 'i2c');
104FTRACE_CATEGORIES.set('irq/*', 'irq');
105FTRACE_CATEGORIES.set('kmem/*', 'kmem');
106FTRACE_CATEGORIES.set('memory_bus/*', 'memory_bus');
107FTRACE_CATEGORIES.set('mmc/*', 'mmc');
108FTRACE_CATEGORIES.set('oom/*', 'oom');
109FTRACE_CATEGORIES.set('power/*', 'power');
110FTRACE_CATEGORIES.set('regulator/*', 'regulator');
111FTRACE_CATEGORIES.set('sched/*', 'sched');
112FTRACE_CATEGORIES.set('sync/*', 'sync');
113FTRACE_CATEGORIES.set('task/*', 'task');
114FTRACE_CATEGORIES.set('task/*', 'task');
115FTRACE_CATEGORIES.set('vmscan/*', 'vmscan');
116FTRACE_CATEGORIES.set('fastrpc/*', 'fastrpc');
117
118function RecSettings(cssClass: string) {
119  const S = (x: number) => x * 1000;
120  const M = (x: number) => x * 1000 * 60;
121  const H = (x: number) => x * 1000 * 60 * 60;
122
123  const cfg = globals.state.recordConfig;
124
125  const recButton = (mode: RecordMode, title: string, img: string) => {
126    const checkboxArgs = {
127      checked: cfg.mode === mode,
128      onchange: (e: InputEvent) => {
129        const checked = (e.target as HTMLInputElement).checked;
130        if (!checked) return;
131        const traceCfg = produce(globals.state.recordConfig, draft => {
132          draft.mode = mode;
133        });
134        globals.dispatch(Actions.setRecordConfig({config: traceCfg}));
135      },
136    };
137    return m(
138        `label${cfg.mode === mode ? '.selected' : ''}`,
139        m(`input[type=radio][name=rec_mode]`, checkboxArgs),
140        m(`img[src=${globals.root}assets/${img}]`),
141        m('span', title));
142  };
143
144  return m(
145      `.record-section${cssClass}`,
146      m('header', 'Recording mode'),
147      m('.record-mode',
148        recButton('STOP_WHEN_FULL', 'Stop when full', 'rec_one_shot.png'),
149        recButton('RING_BUFFER', 'Ring buffer', 'rec_ring_buf.png'),
150        recButton('LONG_TRACE', 'Long trace', 'rec_long_trace.png')),
151
152      m(Slider, {
153        title: 'In-memory buffer size',
154        icon: '360',
155        values: [4, 8, 16, 32, 64, 128, 256, 512],
156        unit: 'MB',
157        set: (cfg, val) => cfg.bufferSizeMb = val,
158        get: (cfg) => cfg.bufferSizeMb
159      } as SliderAttrs),
160
161      m(Slider, {
162        title: 'Max duration',
163        icon: 'timer',
164        values: [S(10), S(15), S(30), S(60), M(5), M(30), H(1), H(6), H(12)],
165        isTime: true,
166        unit: 'h:m:s',
167        set: (cfg, val) => cfg.durationMs = val,
168        get: (cfg) => cfg.durationMs
169      } as SliderAttrs),
170      m(Slider, {
171        title: 'Max file size',
172        icon: 'save',
173        cssClass: cfg.mode !== 'LONG_TRACE' ? '.hide' : '',
174        values: [5, 25, 50, 100, 500, 1000, 1000 * 5, 1000 * 10],
175        unit: 'MB',
176        set: (cfg, val) => cfg.maxFileSizeMb = val,
177        get: (cfg) => cfg.maxFileSizeMb
178      } as SliderAttrs),
179      m(Slider, {
180        title: 'Flush on disk every',
181        cssClass: cfg.mode !== 'LONG_TRACE' ? '.hide' : '',
182        icon: 'av_timer',
183        values: [100, 250, 500, 1000, 2500, 5000],
184        unit: 'ms',
185        set: (cfg, val) => cfg.fileWritePeriodMs = val,
186        get: (cfg) => cfg.fileWritePeriodMs || 0
187      } as SliderAttrs));
188}
189
190function PowerSettings(cssClass: string) {
191  const DOC_URL = 'https://perfetto.dev/docs/data-sources/battery-counters';
192  const descr =
193      [m('div',
194         m('span', `Polls charge counters and instantaneous power draw from
195                    the battery power management IC and the power rails from
196                    the PowerStats HAL (`),
197         m('a', {href: DOC_URL, target: '_blank'}, 'see docs for more'),
198         m('span', ')'))];
199  if (globals.isInternalUser) {
200    descr.push(m(
201        'div',
202        m('span', 'Googlers: See '),
203        m('a',
204          {href: 'http://go/power-rails-internal-doc', target: '_blank'},
205          'this doc'),
206        m('span', ` for instructions on how to change the refault rail selection
207                  on internal devices.`),
208        ));
209  }
210  return m(
211      `.record-section${cssClass}`,
212      m(Probe,
213        {
214          title: 'Battery drain & power rails',
215          img: 'rec_battery_counters.png',
216          descr,
217          setEnabled: (cfg, val) => cfg.batteryDrain = val,
218          isEnabled: (cfg) => cfg.batteryDrain
219        } as ProbeAttrs,
220        m(Slider, {
221          title: 'Poll interval',
222          cssClass: '.thin',
223          values: POLL_INTERVAL_MS,
224          unit: 'ms',
225          set: (cfg, val) => cfg.batteryDrainPollMs = val,
226          get: (cfg) => cfg.batteryDrainPollMs
227        } as SliderAttrs)),
228      m(Probe, {
229        title: 'Board voltages & frequencies',
230        img: 'rec_board_voltage.png',
231        descr: 'Tracks voltage and frequency changes from board sensors',
232        setEnabled: (cfg, val) => cfg.boardSensors = val,
233        isEnabled: (cfg) => cfg.boardSensors
234      } as ProbeAttrs));
235}
236
237function GpuSettings(cssClass: string) {
238  return m(
239      `.record-section${cssClass}`,
240      m(Probe, {
241        title: 'GPU frequency',
242        img: 'rec_cpu_freq.png',
243        descr: 'Records gpu frequency via ftrace',
244        setEnabled: (cfg, val) => cfg.gpuFreq = val,
245        isEnabled: (cfg) => cfg.gpuFreq
246      } as ProbeAttrs),
247      m(Probe, {
248        title: 'GPU memory',
249        img: 'rec_gpu_mem_total.png',
250        descr: `Allows to track per process and global total GPU memory usages.
251                (Available on recent Android 12+ kernels)`,
252        setEnabled: (cfg, val) => cfg.gpuMemTotal = val,
253        isEnabled: (cfg) => cfg.gpuMemTotal
254      } as ProbeAttrs));
255}
256
257function CpuSettings(cssClass: string) {
258  return m(
259      `.record-section${cssClass}`,
260      m(Probe,
261        {
262          title: 'Coarse CPU usage counter',
263          img: 'rec_cpu_coarse.png',
264          descr: `Lightweight polling of CPU usage counters via /proc/stat.
265                    Allows to periodically monitor CPU usage.`,
266          setEnabled: (cfg, val) => cfg.cpuCoarse = val,
267          isEnabled: (cfg) => cfg.cpuCoarse
268        } as ProbeAttrs,
269        m(Slider, {
270          title: 'Poll interval',
271          cssClass: '.thin',
272          values: POLL_INTERVAL_MS,
273          unit: 'ms',
274          set: (cfg, val) => cfg.cpuCoarsePollMs = val,
275          get: (cfg) => cfg.cpuCoarsePollMs
276        } as SliderAttrs)),
277      m(Probe, {
278        title: 'Scheduling details',
279        img: 'rec_cpu_fine.png',
280        descr: 'Enables high-detailed tracking of scheduling events',
281        setEnabled: (cfg, val) => cfg.cpuSched = val,
282        isEnabled: (cfg) => cfg.cpuSched
283      } as ProbeAttrs),
284      m(Probe, {
285        title: 'CPU frequency and idle states',
286        img: 'rec_cpu_freq.png',
287        descr: 'Records cpu frequency and idle state changes via ftrace',
288        setEnabled: (cfg, val) => cfg.cpuFreq = val,
289        isEnabled: (cfg) => cfg.cpuFreq
290      } as ProbeAttrs),
291      m(Probe, {
292        title: 'Syscalls',
293        img: 'rec_syscalls.png',
294        descr: `Tracks the enter and exit of all syscalls. On Android
295                requires a userdebug or eng build.`,
296        setEnabled: (cfg, val) => cfg.cpuSyscall = val,
297        isEnabled: (cfg) => cfg.cpuSyscall
298      } as ProbeAttrs));
299}
300
301function HeapSettings(cssClass: string) {
302  const valuesForMS = [
303    0,
304    1000,
305    10 * 1000,
306    30 * 1000,
307    60 * 1000,
308    5 * 60 * 1000,
309    10 * 60 * 1000,
310    30 * 60 * 1000,
311    60 * 60 * 1000
312  ];
313  const valuesForShMemBuff = [
314    0,
315    512,
316    1024,
317    2 * 1024,
318    4 * 1024,
319    8 * 1024,
320    16 * 1024,
321    32 * 1024,
322    64 * 1024,
323    128 * 1024,
324    256 * 1024,
325    512 * 1024,
326    1024 * 1024,
327    64 * 1024 * 1024,
328    128 * 1024 * 1024,
329    256 * 1024 * 1024,
330    512 * 1024 * 1024
331  ];
332
333  return m(
334      `.${cssClass}`,
335      m(Textarea, {
336        title: 'Names or pids of the processes to track',
337        docsLink:
338            'https://perfetto.dev/docs/data-sources/native-heap-profiler#heapprofd-targets',
339        placeholder: 'One per line, e.g.:\n' +
340            'system_server\n' +
341            'com.google.android.apps.photos\n' +
342            '1503',
343        set: (cfg, val) => cfg.hpProcesses = val,
344        get: (cfg) => cfg.hpProcesses
345      } as TextareaAttrs),
346      m(Slider, {
347        title: 'Sampling interval',
348        cssClass: '.thin',
349        values: [
350          0,     1,     2,      4,      8,      16,     32,   64,
351          128,   256,   512,    1024,   2048,   4096,   8192, 16384,
352          32768, 65536, 131072, 262144, 524288, 1048576
353        ],
354        unit: 'B',
355        min: 0,
356        set: (cfg, val) => cfg.hpSamplingIntervalBytes = val,
357        get: (cfg) => cfg.hpSamplingIntervalBytes
358      } as SliderAttrs),
359      m(Slider, {
360        title: 'Continuous dumps interval ',
361        description: 'Time between following dumps (0 = disabled)',
362        cssClass: '.thin',
363        values: valuesForMS,
364        unit: 'ms',
365        min: 0,
366        set: (cfg, val) => {
367          cfg.hpContinuousDumpsInterval = val;
368        },
369        get: (cfg) => cfg.hpContinuousDumpsInterval
370      } as SliderAttrs),
371      m(Slider, {
372        title: 'Continuous dumps phase',
373        description: 'Time before first dump',
374        cssClass: `.thin${
375            globals.state.recordConfig.hpContinuousDumpsInterval === 0 ?
376                '.greyed-out' :
377                ''}`,
378        values: valuesForMS,
379        unit: 'ms',
380        min: 0,
381        disabled: globals.state.recordConfig.hpContinuousDumpsInterval === 0,
382        set: (cfg, val) => cfg.hpContinuousDumpsPhase = val,
383        get: (cfg) => cfg.hpContinuousDumpsPhase
384      } as SliderAttrs),
385      m(Slider, {
386        title: `Shared memory buffer`,
387        cssClass: '.thin',
388        values: valuesForShMemBuff.filter(
389            value => value === 0 || value >= 8192 && value % 4096 === 0),
390        unit: 'B',
391        min: 0,
392        set: (cfg, val) => cfg.hpSharedMemoryBuffer = val,
393        get: (cfg) => cfg.hpSharedMemoryBuffer
394      } as SliderAttrs),
395      m(Toggle, {
396        title: 'Block client',
397        cssClass: '.thin',
398        descr: `Slow down target application if profiler cannot keep up.`,
399        setEnabled: (cfg, val) => cfg.hpBlockClient = val,
400        isEnabled: (cfg) => cfg.hpBlockClient
401      } as ToggleAttrs),
402      m(Toggle, {
403        title: 'All custom allocators (Q+)',
404        cssClass: '.thin',
405        descr: `If the target application exposes custom allocators, also
406sample from those.`,
407        setEnabled: (cfg, val) => cfg.hpAllHeaps = val,
408        isEnabled: (cfg) => cfg.hpAllHeaps
409      } as ToggleAttrs)
410      // TODO(hjd): Add advanced options.
411  );
412}
413
414function JavaHeapDumpSettings(cssClass: string) {
415  const valuesForMS = [
416    0,
417    1000,
418    10 * 1000,
419    30 * 1000,
420    60 * 1000,
421    5 * 60 * 1000,
422    10 * 60 * 1000,
423    30 * 60 * 1000,
424    60 * 60 * 1000
425  ];
426
427  return m(
428      `.${cssClass}`,
429      m(Textarea, {
430        title: 'Names or pids of the processes to track',
431        placeholder: 'One per line, e.g.:\n' +
432            'com.android.vending\n' +
433            '1503',
434        set: (cfg, val) => cfg.jpProcesses = val,
435        get: (cfg) => cfg.jpProcesses
436      } as TextareaAttrs),
437      m(Slider, {
438        title: 'Continuous dumps interval ',
439        description: 'Time between following dumps (0 = disabled)',
440        cssClass: '.thin',
441        values: valuesForMS,
442        unit: 'ms',
443        min: 0,
444        set: (cfg, val) => {
445          cfg.jpContinuousDumpsInterval = val;
446        },
447        get: (cfg) => cfg.jpContinuousDumpsInterval
448      } as SliderAttrs),
449      m(Slider, {
450        title: 'Continuous dumps phase',
451        description: 'Time before first dump',
452        cssClass: `.thin${
453            globals.state.recordConfig.jpContinuousDumpsInterval === 0 ?
454                '.greyed-out' :
455                ''}`,
456        values: valuesForMS,
457        unit: 'ms',
458        min: 0,
459        disabled: globals.state.recordConfig.jpContinuousDumpsInterval === 0,
460        set: (cfg, val) => cfg.jpContinuousDumpsPhase = val,
461        get: (cfg) => cfg.jpContinuousDumpsPhase
462      } as SliderAttrs),
463  );
464}
465
466function MemorySettings(cssClass: string) {
467  const meminfoOpts = new Map<string, string>();
468  for (const x in MeminfoCounters) {
469    if (typeof MeminfoCounters[x] === 'number' &&
470        !`${x}`.endsWith('_UNSPECIFIED')) {
471      meminfoOpts.set(x, x.replace('MEMINFO_', '').toLowerCase());
472    }
473  }
474  const vmstatOpts = new Map<string, string>();
475  for (const x in VmstatCounters) {
476    if (typeof VmstatCounters[x] === 'number' &&
477        !`${x}`.endsWith('_UNSPECIFIED')) {
478      vmstatOpts.set(x, x.replace('VMSTAT_', '').toLowerCase());
479    }
480  }
481  return m(
482      `.record-section${cssClass}`,
483      m(Probe,
484        {
485          title: 'Native heap profiling',
486          img: 'rec_native_heap_profiler.png',
487          descr: `Track native heap allocations & deallocations of an Android
488               process. (Available on Android 10+)`,
489          setEnabled: (cfg, val) => cfg.heapProfiling = val,
490          isEnabled: (cfg) => cfg.heapProfiling
491        } as ProbeAttrs,
492        HeapSettings(cssClass)),
493      m(Probe,
494        {
495          title: 'Java heap dumps',
496          img: 'rec_java_heap_dump.png',
497          descr: `Dump information about the Java object graph of an
498          Android app. (Available on Android 11+)`,
499          setEnabled: (cfg, val) => cfg.javaHeapDump = val,
500          isEnabled: (cfg) => cfg.javaHeapDump
501        } as ProbeAttrs,
502        JavaHeapDumpSettings(cssClass)),
503      m(Probe,
504        {
505          title: 'Kernel meminfo',
506          img: 'rec_meminfo.png',
507          descr: 'Polling of /proc/meminfo',
508          setEnabled: (cfg, val) => cfg.meminfo = val,
509          isEnabled: (cfg) => cfg.meminfo
510        } as ProbeAttrs,
511        m(Slider, {
512          title: 'Poll interval',
513          cssClass: '.thin',
514          values: POLL_INTERVAL_MS,
515          unit: 'ms',
516          set: (cfg, val) => cfg.meminfoPeriodMs = val,
517          get: (cfg) => cfg.meminfoPeriodMs
518        } as SliderAttrs),
519        m(Dropdown, {
520          title: 'Select counters',
521          cssClass: '.multicolumn',
522          options: meminfoOpts,
523          set: (cfg, val) => cfg.meminfoCounters = val,
524          get: (cfg) => cfg.meminfoCounters
525        } as DropdownAttrs)),
526      m(Probe, {
527        title: 'High-frequency memory events',
528        img: 'rec_mem_hifreq.png',
529        descr: `Allows to track short memory spikes and transitories through
530                ftrace's mm_event, rss_stat and ion events. Available only
531                on recent Android Q+ kernels`,
532        setEnabled: (cfg, val) => cfg.memHiFreq = val,
533        isEnabled: (cfg) => cfg.memHiFreq
534      } as ProbeAttrs),
535      m(Probe, {
536        title: 'Low memory killer',
537        img: 'rec_lmk.png',
538        descr: `Record LMK events. Works both with the old in-kernel LMK
539                and the newer userspace lmkd. It also tracks OOM score
540                adjustments.`,
541        setEnabled: (cfg, val) => cfg.memLmk = val,
542        isEnabled: (cfg) => cfg.memLmk
543      } as ProbeAttrs),
544      m(Probe,
545        {
546          title: 'Per process stats',
547          img: 'rec_ps_stats.png',
548          descr: `Periodically samples all processes in the system tracking:
549                    their thread list, memory counters (RSS, swap and other
550                    /proc/status counters) and oom_score_adj.`,
551          setEnabled: (cfg, val) => cfg.procStats = val,
552          isEnabled: (cfg) => cfg.procStats
553        } as ProbeAttrs,
554        m(Slider, {
555          title: 'Poll interval',
556          cssClass: '.thin',
557          values: POLL_INTERVAL_MS,
558          unit: 'ms',
559          set: (cfg, val) => cfg.procStatsPeriodMs = val,
560          get: (cfg) => cfg.procStatsPeriodMs
561        } as SliderAttrs)),
562      m(Probe,
563        {
564          title: 'Virtual memory stats',
565          img: 'rec_vmstat.png',
566          descr: `Periodically polls virtual memory stats from /proc/vmstat.
567                    Allows to gather statistics about swap, eviction,
568                    compression and pagecache efficiency`,
569          setEnabled: (cfg, val) => cfg.vmstat = val,
570          isEnabled: (cfg) => cfg.vmstat
571        } as ProbeAttrs,
572        m(Slider, {
573          title: 'Poll interval',
574          cssClass: '.thin',
575          values: POLL_INTERVAL_MS,
576          unit: 'ms',
577          set: (cfg, val) => cfg.vmstatPeriodMs = val,
578          get: (cfg) => cfg.vmstatPeriodMs
579        } as SliderAttrs),
580        m(Dropdown, {
581          title: 'Select counters',
582          cssClass: '.multicolumn',
583          options: vmstatOpts,
584          set: (cfg, val) => cfg.vmstatCounters = val,
585          get: (cfg) => cfg.vmstatCounters
586        } as DropdownAttrs)));
587}
588
589
590function AndroidSettings(cssClass: string) {
591  return m(
592      `.record-section${cssClass}`,
593      m(Probe,
594        {
595          title: 'Atrace userspace annotations',
596          img: 'rec_atrace.png',
597          descr: `Enables C++ / Java codebase annotations (ATRACE_BEGIN() /
598                    os.Trace())`,
599          setEnabled: (cfg, val) => cfg.atrace = val,
600          isEnabled: (cfg) => cfg.atrace
601        } as ProbeAttrs,
602        m(Dropdown, {
603          title: 'Categories',
604          cssClass: '.multicolumn.atrace-categories',
605          options: ATRACE_CATEGORIES,
606          set: (cfg, val) => cfg.atraceCats = val,
607          get: (cfg) => cfg.atraceCats
608        } as DropdownAttrs),
609        m(Textarea, {
610          placeholder: 'Extra apps to profile, one per line, e.g.:\n' +
611              'com.android.phone\n' +
612              'com.android.nfc',
613          set: (cfg, val) => cfg.atraceApps = val,
614          get: (cfg) => cfg.atraceApps
615        } as TextareaAttrs)),
616      m(Probe,
617        {
618          title: 'Event log (logcat)',
619          img: 'rec_logcat.png',
620          descr: `Streams the event log into the trace. If no buffer filter is
621                    specified, all buffers are selected.`,
622          setEnabled: (cfg, val) => cfg.androidLogs = val,
623          isEnabled: (cfg) => cfg.androidLogs
624        } as ProbeAttrs,
625        m(Dropdown, {
626          title: 'Buffers',
627          cssClass: '.multicolumn',
628          options: LOG_BUFFERS,
629          set: (cfg, val) => cfg.androidLogBuffers = val,
630          get: (cfg) => cfg.androidLogBuffers
631        } as DropdownAttrs)),
632      m(Probe, {
633        title: 'Frame timeline',
634        img: 'rec_frame_timeline.png',
635        descr: `Records expected/actual frame timings from surface_flinger.
636                    Requires Android 12 (S) or above.`,
637        setEnabled: (cfg, val) => cfg.androidFrameTimeline = val,
638        isEnabled: (cfg) => cfg.androidFrameTimeline
639      } as ProbeAttrs));
640}
641
642
643function ChromeSettings(cssClass: string) {
644  return m(
645      `.record-section${cssClass}`,
646      m(Probe, {
647        title: 'Task scheduling',
648        img: null,
649        descr: `Records events about task scheduling and execution on all
650                  threads`,
651        setEnabled: (cfg, val) => cfg.taskScheduling = val,
652        isEnabled: (cfg) => cfg.taskScheduling
653      } as ProbeAttrs),
654      m(Probe, {
655        title: 'IPC flows',
656        img: null,
657        descr: `Records flow events for passing of IPC messages between
658                processes.`,
659        setEnabled: (cfg, val) => cfg.ipcFlows = val,
660        isEnabled: (cfg) => cfg.ipcFlows
661      } as ProbeAttrs),
662      m(Probe, {
663        title: 'Javascript execution',
664        img: null,
665        descr: `Records events about Javascript execution in the renderer
666                    processes.`,
667        setEnabled: (cfg, val) => cfg.jsExecution = val,
668        isEnabled: (cfg) => cfg.jsExecution
669      } as ProbeAttrs),
670      m(Probe, {
671        title: 'Web content rendering',
672        img: null,
673        descr: `Records events about rendering, layout, and compositing of
674        web content in Blink.`,
675        setEnabled: (cfg, val) => cfg.webContentRendering = val,
676        isEnabled: (cfg) => cfg.webContentRendering
677      } as ProbeAttrs),
678      m(Probe, {
679        title: 'UI rendering & compositing',
680        img: null,
681        descr: `Records events about rendering of browser UI surfaces and
682        compositing of surfaces.`,
683        setEnabled: (cfg, val) => cfg.uiRendering = val,
684        isEnabled: (cfg) => cfg.uiRendering
685      } as ProbeAttrs),
686      m(Probe, {
687        title: 'Input events',
688        img: null,
689        descr: `Records input events and their flow between processes.`,
690        setEnabled: (cfg, val) => cfg.inputEvents = val,
691        isEnabled: (cfg) => cfg.inputEvents
692      } as ProbeAttrs),
693      m(Probe, {
694        title: 'Navigation & Loading',
695        img: null,
696        descr: `Records network events for navigations and resources.`,
697        setEnabled: (cfg, val) => cfg.navigationAndLoading = val,
698        isEnabled: (cfg) => cfg.navigationAndLoading
699      } as ProbeAttrs),
700      m(Probe, {
701        title: 'Chrome Logs',
702        img: null,
703        descr: `Records Chrome log messages`,
704        setEnabled: (cfg, val) => cfg.chromeLogs = val,
705        isEnabled: (cfg) => cfg.chromeLogs
706      } as ProbeAttrs),
707      ChromeCategoriesSelection());
708}
709
710function ChromeCategoriesSelection() {
711  // If we are attempting to record via the Chrome extension, we receive the
712  // list of actually supported categories via DevTools. Otherwise, we fall back
713  // to an integrated list of categories from a recent version of Chrome.
714  let categories = globals.state.chromeCategories;
715  if (!categories || !isChromeTarget(globals.state.recordingTarget)) {
716    categories = getBuiltinChromeCategoryList();
717  }
718
719  // Show "disabled-by-default" categories last.
720  const categoriesMap = new Map<string, string>();
721  const disabledByDefaultCategories: string[] = [];
722  const disabledPrefix = 'disabled-by-default-';
723  categories.forEach(cat => {
724    if (cat.startsWith(disabledPrefix)) {
725      disabledByDefaultCategories.push(cat);
726    } else {
727      categoriesMap.set(cat, cat);
728    }
729  });
730  disabledByDefaultCategories.forEach(cat => {
731    categoriesMap.set(
732        cat, `${cat.replace(disabledPrefix, '')} (high overhead)`);
733  });
734
735  return m(Dropdown, {
736    title: 'Additional Chrome categories',
737    cssClass: '.multicolumn.two-columns',
738    options: categoriesMap,
739    set: (cfg, val) => cfg.chromeCategoriesSelected = val,
740    get: (cfg) => cfg.chromeCategoriesSelected
741  } as DropdownAttrs);
742}
743
744function AdvancedSettings(cssClass: string) {
745  const S = (x: number) => x * 1000;
746  const M = (x: number) => x * 1000 * 60;
747  return m(
748      `.record-section${cssClass}`,
749      m(Probe,
750        {
751          title: 'Advanced ftrace config',
752          img: 'rec_ftrace.png',
753          descr: `Enable individual events and tune the kernel-tracing (ftrace)
754                  module. The events enabled here are in addition to those from
755                  enabled by other probes.`,
756          setEnabled: (cfg, val) => cfg.ftrace = val,
757          isEnabled: (cfg) => cfg.ftrace
758        } as ProbeAttrs,
759        m(Slider, {
760          title: 'Buf size',
761          cssClass: '.thin',
762          values: [512, 1024, 2 * 1024, 4 * 1024, 16 * 1024, 32 * 1024],
763          unit: 'KB',
764          set: (cfg, val) => cfg.ftraceBufferSizeKb = val,
765          get: (cfg) => cfg.ftraceBufferSizeKb
766        } as SliderAttrs),
767        m(Slider, {
768          title: 'Drain rate',
769          cssClass: '.thin',
770          values: [100, 250, 500, 1000, 2500, 5000],
771          unit: 'ms',
772          set: (cfg, val) => cfg.ftraceDrainPeriodMs = val,
773          get: (cfg) => cfg.ftraceDrainPeriodMs
774        } as SliderAttrs),
775        m(Dropdown, {
776          title: 'Event groups',
777          cssClass: '.multicolumn.ftrace-events',
778          options: FTRACE_CATEGORIES,
779          set: (cfg, val) => cfg.ftraceEvents = val,
780          get: (cfg) => cfg.ftraceEvents
781        } as DropdownAttrs),
782        m(Textarea, {
783          placeholder: 'Add extra events, one per line, e.g.:\n' +
784              'sched/sched_switch\n' +
785              'kmem/*',
786          set: (cfg, val) => cfg.ftraceExtraEvents = val,
787          get: (cfg) => cfg.ftraceExtraEvents
788        } as TextareaAttrs)),
789      globals.state.videoEnabled ?
790          m(Probe,
791            {
792              title: 'Screen recording',
793              img: null,
794              descr: `Records the screen along with running a trace. Max
795                  time of recording is 3 minutes (180 seconds).`,
796              setEnabled: (cfg, val) => cfg.screenRecord = val,
797              isEnabled: (cfg) => cfg.screenRecord,
798            } as ProbeAttrs,
799            m(Slider, {
800              title: 'Max duration',
801              icon: 'timer',
802              values: [S(10), S(15), S(30), S(60), M(2), M(3)],
803              isTime: true,
804              unit: 'm:s',
805              set: (cfg, val) => cfg.durationMs = val,
806              get: (cfg) => cfg.durationMs,
807            } as SliderAttrs)) :
808          null);
809}
810
811function RecordHeader() {
812  return m(
813      '.record-header',
814      m('.top-part',
815        m('.target-and-status',
816          RecordingPlatformSelection(),
817          RecordingStatusLabel(),
818          ErrorLabel()),
819        recordingButtons()),
820      RecordingNotes());
821}
822
823function RecordingPlatformSelection() {
824  if (globals.state.recordingInProgress) return [];
825
826  const availableAndroidDevices = globals.state.availableAdbDevices;
827  const recordingTarget = globals.state.recordingTarget;
828
829  const targets = [];
830  for (const {os, name} of getDefaultRecordingTargets()) {
831    targets.push(m('option', {value: os}, name));
832  }
833  for (const d of availableAndroidDevices) {
834    targets.push(m('option', {value: d.serial}, d.name));
835  }
836
837  const selectedIndex = isAdbTarget(recordingTarget) ?
838      targets.findIndex(node => node.attrs.value === recordingTarget.serial) :
839      targets.findIndex(node => node.attrs.value === recordingTarget.os);
840
841  return m(
842      '.target',
843      m(
844          'label',
845          'Target platform:',
846          m('select',
847            {
848              selectedIndex,
849              onchange: (e: Event) => {
850                onTargetChange((e.target as HTMLSelectElement).value);
851              },
852              onupdate: (select) => {
853                // Work around mithril bug
854                // (https://github.com/MithrilJS/mithril.js/issues/2107): We may
855                // update the select's options while also changing the
856                // selectedIndex at the same time. The update of selectedIndex
857                // may be applied before the new options are added to the select
858                // element. Because the new selectedIndex may be outside of the
859                // select's options at that time, we have to reselect the
860                // correct index here after any new children were added.
861                (select.dom as HTMLSelectElement).selectedIndex = selectedIndex;
862              }
863            },
864            ...targets),
865          ),
866      m('.chip',
867        {onclick: addAndroidDevice},
868        m('button', 'Add ADB Device'),
869        m('i.material-icons', 'add')));
870}
871
872// |target| can be the TargetOs or the android serial.
873function onTargetChange(target: string) {
874  const recordingTarget: RecordingTarget =
875      globals.state.availableAdbDevices.find(d => d.serial === target) ||
876      getDefaultRecordingTargets().find(t => t.os === target) ||
877      getDefaultRecordingTargets()[0];
878
879  if (isChromeTarget(recordingTarget)) {
880    globals.dispatch(Actions.setUpdateChromeCategories({update: true}));
881  }
882
883  globals.dispatch(Actions.setRecordingTarget({target: recordingTarget}));
884  globals.rafScheduler.scheduleFullRedraw();
885}
886
887function Instructions(cssClass: string) {
888  return m(
889      `.record-section.instructions${cssClass}`,
890      m('header', 'Recording command'),
891      localStorage.hasOwnProperty(LOCAL_STORAGE_SHOW_CONFIG) ?
892          m('button.permalinkconfig',
893            {
894              onclick: () => {
895                globals.dispatch(
896                    Actions.createPermalink({isRecordingConfig: true}));
897              },
898            },
899            'Share recording settings') :
900          null,
901      RecordingSnippet(),
902      BufferUsageProgressBar(),
903      m('.buttons', StopCancelButtons()),
904      recordingLog());
905}
906
907function displayRecordConfigs() {
908  return recordConfigStore.recordConfigs.map((item) => {
909    return m('.config', [
910      m('span.title-config', item.title),
911      m('button',
912        {
913          class: 'config-button load',
914          onclick: () => {
915            globals.dispatch(Actions.setRecordConfig({config: item.config}));
916            globals.rafScheduler.scheduleFullRedraw();
917          }
918        },
919        'load'),
920      m('button',
921        {
922          class: 'config-button delete',
923          onclick: () => {
924            recordConfigStore.delete(item.key);
925            globals.rafScheduler.scheduleFullRedraw();
926          }
927        },
928        'delete'),
929    ]);
930  });
931}
932
933function getSavedConfigList() {
934  if (recordConfigStore.recordConfigs.length === 0) {
935    return [];
936  }
937  return displayRecordConfigs();
938}
939
940export const ConfigTitleState = {
941  title: '',
942  getTitle: () => {
943    return ConfigTitleState.title;
944  },
945  setTitle: (value: string) => {
946    ConfigTitleState.title = value;
947  },
948  clearTitle: () => {
949    ConfigTitleState.title = '';
950  }
951};
952
953function Configurations(cssClass: string) {
954  return m(
955      `.record-section${cssClass}`,
956      m('header', 'Save and load configurations'),
957      m('.input-config',
958        [
959          m('input', {
960            value: ConfigTitleState.title,
961            placeholder: 'Title for config',
962            oninput() {
963              ConfigTitleState.setTitle(this.value);
964            }
965          }),
966          m('button',
967            {
968              class: 'config-button save',
969              onclick: () => {
970                recordConfigStore.save(
971                    globals.state.recordConfig, ConfigTitleState.getTitle());
972                globals.rafScheduler.scheduleFullRedraw();
973                ConfigTitleState.clearTitle();
974              }
975            },
976            'Save current config')
977        ]),
978      getSavedConfigList());
979}
980
981function BufferUsageProgressBar() {
982  if (!globals.state.recordingInProgress) return [];
983
984  const bufferUsage = globals.bufferUsage ? globals.bufferUsage : 0.0;
985  // Buffer usage is not available yet on Android.
986  if (bufferUsage === 0) return [];
987
988  return m(
989      'label',
990      'Buffer usage: ',
991      m('progress', {max: 100, value: bufferUsage * 100}));
992}
993
994function RecordingNotes() {
995  const sideloadUrl =
996      'https://perfetto.dev/docs/contributing/build-instructions#get-the-code';
997  const linuxUrl = 'https://perfetto.dev/docs/quickstart/linux-tracing';
998  const cmdlineUrl =
999      'https://perfetto.dev/docs/quickstart/android-tracing#perfetto-cmdline';
1000  const extensionURL = `https://chrome.google.com/webstore/detail/
1001      perfetto-ui/lfmkphfpdbjijhpomgecfikhfohaoine`;
1002
1003  const notes: m.Children = [];
1004
1005  const msgFeatNotSupported =
1006      m('span', `Some probes are only supported in Perfetto versions running
1007      on Android Q+. `);
1008
1009  const msgPerfettoNotSupported =
1010      m('span', `Perfetto is not supported natively before Android P. `);
1011
1012  const msgSideload =
1013      m('span',
1014        `If you have a rooted device you can `,
1015        m('a',
1016          {href: sideloadUrl, target: '_blank'},
1017          `sideload the latest version of
1018         Perfetto.`));
1019
1020  const msgRecordingNotSupported =
1021      m('.note',
1022        `Recording Perfetto traces from the UI is not supported natively
1023     before Android Q. If you are using a P device, please select 'Android P'
1024     as the 'Target Platform' and `,
1025        m('a',
1026          {href: cmdlineUrl, target: '_blank'},
1027          `collect the trace using ADB.`));
1028
1029  const msgChrome =
1030      m('.note',
1031        `To trace Chrome from the Perfetto UI, you need to install our `,
1032        m('a', {href: extensionURL, target: '_blank'}, 'Chrome extension'),
1033        ' and then reload this page.');
1034
1035  const msgLinux =
1036      m('.note',
1037        `Use this `,
1038        m('a', {href: linuxUrl, target: '_blank'}, `quickstart guide`),
1039        ` to get started with tracing on Linux.`);
1040
1041  const msgLongTraces = m(
1042      '.note',
1043      `Recording in long trace mode through the UI is not supported. Please copy
1044    the command and `,
1045      m('a',
1046        {href: cmdlineUrl, target: '_blank'},
1047        `collect the trace using ADB.`));
1048
1049  const msgZeroProbes =
1050      m('.note',
1051        'It looks like you didn\'t add any probes. ' +
1052            'Please add at least one to get a non-empty trace.');
1053
1054  if (!hasActiveProbes(globals.state.recordConfig)) {
1055    notes.push(msgZeroProbes);
1056  }
1057
1058  if (isAdbTarget(globals.state.recordingTarget)) {
1059    notes.push(msgRecordingNotSupported);
1060  }
1061  switch (globals.state.recordingTarget.os) {
1062    case 'Q':
1063      break;
1064    case 'P':
1065      notes.push(m('.note', msgFeatNotSupported, msgSideload));
1066      break;
1067    case 'O':
1068      notes.push(m('.note', msgPerfettoNotSupported, msgSideload));
1069      break;
1070    case 'L':
1071      notes.push(msgLinux);
1072      break;
1073    case 'C':
1074      if (!globals.state.extensionInstalled) notes.push(msgChrome);
1075      break;
1076    case 'CrOS':
1077      if (!globals.state.extensionInstalled) notes.push(msgChrome);
1078      break;
1079    default:
1080  }
1081  if (globals.state.recordConfig.mode === 'LONG_TRACE') {
1082    notes.unshift(msgLongTraces);
1083  }
1084
1085  return notes.length > 0 ? m('div', notes) : [];
1086}
1087
1088function RecordingSnippet() {
1089  const target = globals.state.recordingTarget;
1090
1091  // We don't need commands to start tracing on chrome
1092  if (isChromeTarget(target)) {
1093    return globals.state.extensionInstalled ?
1094        m('div',
1095          m('label',
1096            `To trace Chrome from the Perfetto UI you just have to press
1097         'Start Recording'.`)) :
1098        [];
1099  }
1100  return m(CodeSnippet, {text: getRecordCommand(target)});
1101}
1102
1103function getRecordCommand(target: RecordingTarget) {
1104  const data = globals.trackDataStore.get('config') as
1105          {commandline: string, pbtxt: string, pbBase64: string} |
1106      null;
1107
1108  const cfg = globals.state.recordConfig;
1109  let time = cfg.durationMs / 1000;
1110
1111  if (time > MAX_TIME) {
1112    time = MAX_TIME;
1113  }
1114
1115  const pbBase64 = data ? data.pbBase64 : '';
1116  const pbtx = data ? data.pbtxt : '';
1117  let cmd = '';
1118  if (cfg.screenRecord) {
1119    // Half-second delay to ensure Perfetto starts tracing before screenrecord
1120    // starts recording
1121    cmd += `(sleep 0.5 && adb shell screenrecord --time-limit ${time}`;
1122    cmd += ' "/sdcard/tracescr.mp4") &\\\n';
1123  }
1124  if (isAndroidP(target)) {
1125    cmd += `echo '${pbBase64}' | \n`;
1126    cmd += 'base64 --decode | \n';
1127    cmd += 'adb shell "perfetto -c - -o /data/misc/perfetto-traces/trace"\n';
1128  } else {
1129    cmd +=
1130        isAndroidTarget(target) ? 'adb shell perfetto \\\n' : 'perfetto \\\n';
1131    cmd += '  -c - --txt \\\n';
1132    cmd += '  -o /data/misc/perfetto-traces/trace \\\n';
1133    cmd += '<<EOF\n\n';
1134    cmd += pbtx;
1135    cmd += '\nEOF\n';
1136  }
1137  return cmd;
1138}
1139
1140function recordingButtons() {
1141  const state = globals.state;
1142  const target = state.recordingTarget;
1143  const recInProgress = state.recordingInProgress;
1144
1145  const start =
1146      m(`button`,
1147        {
1148          class: recInProgress ? '' : 'selected',
1149          onclick: onStartRecordingPressed
1150        },
1151        'Start Recording');
1152
1153  const buttons: m.Children = [];
1154
1155  if (isAndroidTarget(target)) {
1156    if (!recInProgress && isAdbTarget(target) &&
1157        globals.state.recordConfig.mode !== 'LONG_TRACE') {
1158      buttons.push(start);
1159    }
1160  } else if (isChromeTarget(target) && state.extensionInstalled) {
1161    buttons.push(start);
1162  }
1163  return m('.button', buttons);
1164}
1165
1166function StopCancelButtons() {
1167  if (!globals.state.recordingInProgress) return [];
1168
1169  const stop =
1170      m(`button.selected`,
1171        {onclick: () => globals.dispatch(Actions.stopRecording({}))},
1172        'Stop');
1173
1174  const cancel =
1175      m(`button`,
1176        {onclick: () => globals.dispatch(Actions.cancelRecording({}))},
1177        'Cancel');
1178
1179  return [stop, cancel];
1180}
1181
1182function onStartRecordingPressed() {
1183  location.href = '#!/record?p=instructions';
1184  globals.rafScheduler.scheduleFullRedraw();
1185
1186  const target = globals.state.recordingTarget;
1187  if (isAndroidTarget(target) || isChromeTarget(target)) {
1188    globals.logging.logEvent('Record Trace', `Record trace (${target.os})`);
1189    globals.dispatch(Actions.startRecording({}));
1190  }
1191}
1192
1193function RecordingStatusLabel() {
1194  const recordingStatus = globals.state.recordingStatus;
1195  if (!recordingStatus) return [];
1196  return m('label', recordingStatus);
1197}
1198
1199function ErrorLabel() {
1200  const lastRecordingError = globals.state.lastRecordingError;
1201  if (!lastRecordingError) return [];
1202  return m('label.error-label', `Error:  ${lastRecordingError}`);
1203}
1204
1205function recordingLog() {
1206  const logs = globals.recordingLog;
1207  if (logs === undefined) return [];
1208  return m('.code-snippet.no-top-bar', m('code', logs));
1209}
1210
1211// The connection must be done in the frontend. After it, the serial ID will
1212// be inserted in the state, and the worker will be able to connect to the
1213// correct device.
1214async function addAndroidDevice() {
1215  let device: USBDevice;
1216  try {
1217    device = await new AdbOverWebUsb().findDevice();
1218  } catch (e) {
1219    const err = `No device found: ${e.name}: ${e.message}`;
1220    console.error(err, e);
1221    alert(err);
1222    return;
1223  }
1224
1225  if (!device.serialNumber) {
1226    console.error('serial number undefined');
1227    return;
1228  }
1229
1230  // After the user has selected a device with the chrome UI, it will be
1231  // available when listing all the available device from WebUSB. Therefore,
1232  // we update the list of available devices.
1233  await updateAvailableAdbDevices(device.serialNumber);
1234}
1235
1236export async function updateAvailableAdbDevices(
1237    preferredDeviceSerial?: string) {
1238  const devices = await new AdbOverWebUsb().getPairedDevices();
1239
1240  let recordingTarget: AdbRecordingTarget|undefined = undefined;
1241
1242  const availableAdbDevices: AdbRecordingTarget[] = [];
1243  devices.forEach(d => {
1244    if (d.productName && d.serialNumber) {
1245      // TODO(nicomazz): At this stage, we can't know the OS version, so we
1246      // assume it is 'Q'. This can create problems with devices with an old
1247      // version of perfetto. The os detection should be done after the adb
1248      // connection, from adb_record_controller
1249      availableAdbDevices.push(
1250          {name: d.productName, serial: d.serialNumber, os: 'Q'});
1251      if (preferredDeviceSerial && preferredDeviceSerial === d.serialNumber) {
1252        recordingTarget = availableAdbDevices[availableAdbDevices.length - 1];
1253      }
1254    }
1255  });
1256
1257  globals.dispatch(
1258      Actions.setAvailableAdbDevices({devices: availableAdbDevices}));
1259  selectAndroidDeviceIfAvailable(availableAdbDevices, recordingTarget);
1260  globals.rafScheduler.scheduleFullRedraw();
1261  return availableAdbDevices;
1262}
1263
1264function selectAndroidDeviceIfAvailable(
1265    availableAdbDevices: AdbRecordingTarget[],
1266    recordingTarget?: RecordingTarget) {
1267  if (!recordingTarget) {
1268    recordingTarget = globals.state.recordingTarget;
1269  }
1270  const deviceConnected = isAdbTarget(recordingTarget);
1271  const connectedDeviceDisconnected = deviceConnected &&
1272      availableAdbDevices.find(
1273          e => e.serial === (recordingTarget as AdbRecordingTarget).serial) ===
1274          undefined;
1275
1276  if (availableAdbDevices.length) {
1277    // If there's an Android device available and the current selection isn't
1278    // one, select the Android device by default. If the current device isn't
1279    // available anymore, but another Android device is, select the other
1280    // Android device instead.
1281    if (!deviceConnected || connectedDeviceDisconnected) {
1282      recordingTarget = availableAdbDevices[0];
1283    }
1284
1285    globals.dispatch(Actions.setRecordingTarget({target: recordingTarget}));
1286    return;
1287  }
1288
1289  // If the currently selected device was disconnected, reset the recording
1290  // target to the default one.
1291  if (connectedDeviceDisconnected) {
1292    globals.dispatch(
1293        Actions.setRecordingTarget({target: getDefaultRecordingTargets()[0]}));
1294  }
1295}
1296
1297function recordMenu(routePage: string) {
1298  const target = globals.state.recordingTarget;
1299  const chromeProbe =
1300      m('a[href="#!/record?p=chrome"]',
1301        m(`li${routePage === 'chrome' ? '.active' : ''}`,
1302          m('i.material-icons', 'laptop_chromebook'),
1303          m('.title', 'Chrome'),
1304          m('.sub', 'Chrome traces')));
1305  const recInProgress = globals.state.recordingInProgress;
1306
1307  return m(
1308      '.record-menu',
1309      {
1310        class: recInProgress ? 'disabled' : '',
1311        onclick: () => globals.rafScheduler.scheduleFullRedraw()
1312      },
1313      m('header', 'Trace config'),
1314      m('ul',
1315        m('a[href="#!/record?p=buffers"]',
1316          m(`li${routePage === 'buffers' ? '.active' : ''}`,
1317            m('i.material-icons', 'tune'),
1318            m('.title', 'Recording settings'),
1319            m('.sub', 'Buffer mode, size and duration'))),
1320        m('a[href="#!/record?p=instructions"]',
1321          m(`li${routePage === 'instructions' ? '.active' : ''}`,
1322            m('i.material-icons.rec', 'fiber_manual_record'),
1323            m('.title', 'Recording command'),
1324            m('.sub', 'Manually record trace'))),
1325        localStorage.hasOwnProperty(LOCAL_STORAGE_SHOW_CONFIG) ?
1326            m('a[href="#!/record?p=config"]',
1327              {
1328                onclick: () => {
1329                  recordConfigStore.reloadFromLocalStorage();
1330                }
1331              },
1332              m(`li${routePage === 'config' ? '.active' : ''}`,
1333                m('i.material-icons', 'tune'),
1334                m('.title', 'Saved configs'),
1335                m('.sub', 'Manage local configs'))) :
1336            null),
1337      m('header', 'Probes'),
1338      m('ul',
1339        isChromeTarget(target) && !isCrOSTarget(target) ? [chromeProbe] : [
1340          m('a[href="#!/record?p=cpu"]',
1341            m(`li${routePage === 'cpu' ? '.active' : ''}`,
1342              m('i.material-icons', 'subtitles'),
1343              m('.title', 'CPU'),
1344              m('.sub', 'CPU usage, scheduling, wakeups'))),
1345          m('a[href="#!/record?p=gpu"]',
1346            m(`li${routePage === 'gpu' ? '.active' : ''}`,
1347              m('i.material-icons', 'aspect_ratio'),
1348              m('.title', 'GPU'),
1349              m('.sub', 'GPU frequency, memory'))),
1350          m('a[href="#!/record?p=power"]',
1351            m(`li${routePage === 'power' ? '.active' : ''}`,
1352              m('i.material-icons', 'battery_charging_full'),
1353              m('.title', 'Power'),
1354              m('.sub', 'Battery and other energy counters'))),
1355          m('a[href="#!/record?p=memory"]',
1356            m(`li${routePage === 'memory' ? '.active' : ''}`,
1357              m('i.material-icons', 'memory'),
1358              m('.title', 'Memory'),
1359              m('.sub', 'Physical mem, VM, LMK'))),
1360          m('a[href="#!/record?p=android"]',
1361            m(`li${routePage === 'android' ? '.active' : ''}`,
1362              m('i.material-icons', 'android'),
1363              m('.title', 'Android apps & svcs'),
1364              m('.sub', 'atrace and logcat'))),
1365          chromeProbe,
1366          m('a[href="#!/record?p=advanced"]',
1367            m(`li${routePage === 'advanced' ? '.active' : ''}`,
1368              m('i.material-icons', 'settings'),
1369              m('.title', 'Advanced settings'),
1370              m('.sub', 'Complicated stuff for wizards')))
1371        ]));
1372}
1373
1374
1375export const RecordPage = createPage({
1376  view() {
1377    const SECTIONS: {[property: string]: (cssClass: string) => m.Child} = {
1378      buffers: RecSettings,
1379      instructions: Instructions,
1380      config: Configurations,
1381      cpu: CpuSettings,
1382      gpu: GpuSettings,
1383      power: PowerSettings,
1384      memory: MemorySettings,
1385      android: AndroidSettings,
1386      chrome: ChromeSettings,
1387      advanced: AdvancedSettings,
1388    };
1389
1390    const pages: m.Children = [];
1391    const routePageParam = Router.param('p');
1392    let routePage = typeof routePageParam === 'string' ? routePageParam : '';
1393    if (!Object.keys(SECTIONS).includes(routePage)) {
1394      routePage = 'buffers';
1395    }
1396    for (const key of Object.keys(SECTIONS)) {
1397      const cssClass = routePage === key ? '.active' : '';
1398      pages.push(SECTIONS[key](cssClass));
1399    }
1400
1401    return m(
1402        '.record-page',
1403        globals.state.recordingInProgress ? m('.hider') : [],
1404        m('.record-container', RecordHeader(), recordMenu(routePage), pages));
1405  }
1406});
1407