• 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
15import {produce} from 'immer';
16import * as m from 'mithril';
17
18import {Actions} from '../common/actions';
19import {MeminfoCounters, VmstatCounters} from '../common/protos';
20import {RecordMode} from '../common/state';
21
22import {globals} from './globals';
23import {createPage} from './pages';
24import {
25  CodeSnippet,
26  Dropdown,
27  DropdownAttrs,
28  Probe,
29  ProbeAttrs,
30  Slider,
31  SliderAttrs,
32  Textarea,
33  TextareaAttrs
34} from './record_widgets';
35import {Router} from './router';
36
37
38const POLL_RATE_MS = [250, 500, 1000, 2500, 5000, 30000, 60000];
39
40const ATRACE_CATEGORIES = new Map<string, string>();
41ATRACE_CATEGORIES.set('gfx', 'Graphics');
42ATRACE_CATEGORIES.set('input', 'Input');
43ATRACE_CATEGORIES.set('view', 'View System');
44ATRACE_CATEGORIES.set('webview', 'WebView');
45ATRACE_CATEGORIES.set('wm', 'Window Manager');
46ATRACE_CATEGORIES.set('am', 'Activity Manager');
47ATRACE_CATEGORIES.set('sm', 'Sync Manager');
48ATRACE_CATEGORIES.set('audio', 'Audio');
49ATRACE_CATEGORIES.set('video', 'Video');
50ATRACE_CATEGORIES.set('camera', 'Camera');
51ATRACE_CATEGORIES.set('hal', 'Hardware Modules');
52ATRACE_CATEGORIES.set('res', 'Resource Loading');
53ATRACE_CATEGORIES.set('dalvik', 'ART & Dalvik');
54ATRACE_CATEGORIES.set('rs', 'RenderScript');
55ATRACE_CATEGORIES.set('bionic', 'Bionic C library');
56ATRACE_CATEGORIES.set('gfx', 'Graphics');
57ATRACE_CATEGORIES.set('power', 'Power Management');
58ATRACE_CATEGORIES.set('pm', 'Package Manager');
59ATRACE_CATEGORIES.set('ss', 'System Server');
60ATRACE_CATEGORIES.set('database', 'Database');
61ATRACE_CATEGORIES.set('network', 'Network');
62ATRACE_CATEGORIES.set('adb', 'ADB');
63ATRACE_CATEGORIES.set('vibrator', 'Vibrator');
64ATRACE_CATEGORIES.set('aidl', 'AIDL calls');
65ATRACE_CATEGORIES.set('nnapi', 'Neural Network API');
66ATRACE_CATEGORIES.set('rro', 'Resource Overlay');
67
68const LOG_BUFFERS = new Map<string, string>();
69LOG_BUFFERS.set('LID_RADIO', 'Radio');
70LOG_BUFFERS.set('LID_EVENTS', 'Binary events');
71LOG_BUFFERS.set('LID_SYSTEM', 'System');
72LOG_BUFFERS.set('LID_CRASH', 'Crash');
73LOG_BUFFERS.set('LID_SECURITY', 'Security');
74LOG_BUFFERS.set('LID_KERNEL', 'Kernel');
75
76const FTRACE_CATEGORIES = new Map<string, string>();
77FTRACE_CATEGORIES.set('binder/*', 'binder');
78FTRACE_CATEGORIES.set('block/*', 'block');
79FTRACE_CATEGORIES.set('clk/*', 'clk');
80FTRACE_CATEGORIES.set('ext4/*', 'ext4');
81FTRACE_CATEGORIES.set('f2fs/*', 'f2fs');
82FTRACE_CATEGORIES.set('i2c/*', 'i2c');
83FTRACE_CATEGORIES.set('irq/*', 'irq');
84FTRACE_CATEGORIES.set('kmem/*', 'kmem');
85FTRACE_CATEGORIES.set('memory_bus/*', 'memory_bus');
86FTRACE_CATEGORIES.set('mmc/*', 'mmc');
87FTRACE_CATEGORIES.set('oom/*', 'oom');
88FTRACE_CATEGORIES.set('power/*', 'power');
89FTRACE_CATEGORIES.set('regulator/*', 'regulator');
90FTRACE_CATEGORIES.set('sched/*', 'sched');
91FTRACE_CATEGORIES.set('sync/*', 'sync');
92FTRACE_CATEGORIES.set('task/*', 'task');
93FTRACE_CATEGORIES.set('task/*', 'task');
94FTRACE_CATEGORIES.set('vmscan/*', 'vmscan');
95
96function RecSettings(cssClass: string) {
97  const S = (x: number) => x * 1000;
98  const M = (x: number) => x * 1000 * 60;
99  const H = (x: number) => x * 1000 * 60 * 60;
100
101  const cfg = globals.state.recordConfig;
102
103  const recButton = (mode: RecordMode, title: string, img: string) => {
104    const checkboxArgs = {
105      checked: cfg.mode === mode,
106      onchange: m.withAttr(
107          'checked',
108          (checked: boolean) => {
109            if (!checked) return;
110            const traceCfg = produce(globals.state.recordConfig, draft => {
111              draft.mode = mode;
112            });
113            globals.dispatch(Actions.setRecordConfig({config: traceCfg}));
114          })
115    };
116    return m(
117        `label${cfg.mode === mode ? '.selected' : ''}`,
118        m(`input[type=radio][name=rec_mode]`, checkboxArgs),
119        m(`img[src=assets/${img}]`),
120        m('span', title));
121  };
122
123  return m(
124      `.record-section${cssClass}`,
125      m('header', 'Recording mode'),
126      m('.record-mode',
127        recButton('STOP_WHEN_FULL', 'Stop when full', 'rec_one_shot.png'),
128        recButton('RING_BUFFER', 'Ring buffer', 'rec_ring_buf.png'),
129        recButton('LONG_TRACE', 'Long trace', 'rec_long_trace.png'), ),
130
131      m(Slider, {
132        title: 'In-memory buffer size',
133        icon: '360',
134        values: [4, 8, 16, 32, 64, 128, 256, 512],
135        unit: 'MB',
136        set: (cfg, val) => cfg.bufferSizeMb = val,
137        get: (cfg) => cfg.bufferSizeMb
138      } as SliderAttrs),
139
140      m(Slider, {
141        title: 'Max duration',
142        icon: 'timer',
143        values: [S(10), S(15), S(30), S(60), M(5), M(30), H(1), H(6), H(12)],
144        isTime: true,
145        unit: 'h:m:s',
146        set: (cfg, val) => cfg.durationMs = val,
147        get: (cfg) => cfg.durationMs
148      } as SliderAttrs),
149      m(Slider, {
150        title: 'Max file size',
151        icon: 'save',
152        cssClass: cfg.mode !== 'LONG_TRACE' ? '.hide' : '',
153        values: [5, 25, 50, 100, 500, 1000, 1000 * 5, 1000 * 10],
154        unit: 'MB',
155        set: (cfg, val) => cfg.maxFileSizeMb = val,
156        get: (cfg) => cfg.maxFileSizeMb
157      } as SliderAttrs),
158      m(Slider, {
159        title: 'Flush on disk every',
160        cssClass: cfg.mode !== 'LONG_TRACE' ? '.hide' : '',
161        icon: 'av_timer',
162        values: [100, 250, 500, 1000, 2500, 5000],
163        unit: 'ms',
164        set: (cfg, val) => cfg.fileWritePeriodMs = val,
165        get: (cfg) => cfg.fileWritePeriodMs || 0
166      } as SliderAttrs));
167}
168
169function PowerSettings(cssClass: string) {
170  return m(
171      `.record-section${cssClass}`,
172      m(Probe,
173        {
174          title: 'Battery drain',
175          img: 'rec_battery_counters.png',
176          descr: `Polls charge counters and instantaneous power draw from
177                    the battery power management IC.`,
178          setEnabled: (cfg, val) => cfg.batteryDrain = val,
179          isEnabled: (cfg) => cfg.batteryDrain
180        } as ProbeAttrs,
181        m(Slider, {
182          title: 'Poll rate',
183          cssClass: '.thin',
184          values: POLL_RATE_MS,
185          unit: 'ms',
186          set: (cfg, val) => cfg.batteryDrainPollMs = val,
187          get: (cfg) => cfg.batteryDrainPollMs
188        } as SliderAttrs)),
189      m(Probe, {
190        title: 'Board voltages & frequencies',
191        img: 'rec_board_voltage.png',
192        descr: 'Tracks voltage and frequency changes from board sensors',
193        setEnabled: (cfg, val) => cfg.boardSensors = val,
194        isEnabled: (cfg) => cfg.boardSensors
195      } as ProbeAttrs));
196}
197
198function CpuSettings(cssClass: string) {
199  return m(
200      `.record-section${cssClass}`,
201      m(Probe,
202        {
203          title: 'Coarse CPU usage counter',
204          img: 'rec_cpu_coarse.png',
205          descr: `Lightweight polling of CPU usage counters via /proc/stat.
206                    Allows to periodically monitor CPU usage.`,
207          setEnabled: (cfg, val) => cfg.cpuCoarse = val,
208          isEnabled: (cfg) => cfg.cpuCoarse
209        } as ProbeAttrs,
210        m(Slider, {
211          title: 'Poll rate',
212          cssClass: '.thin',
213          values: POLL_RATE_MS,
214          unit: 'ms',
215          set: (cfg, val) => cfg.cpuCoarsePollMs = val,
216          get: (cfg) => cfg.cpuCoarsePollMs
217        } as SliderAttrs)),
218      m(Probe, {
219        title: 'Scheduling details',
220        img: 'rec_cpu_fine.png',
221        descr: 'Enables high-detailed tracking of scheduling events',
222        setEnabled: (cfg, val) => cfg.cpuSched = val,
223        isEnabled: (cfg) => cfg.cpuSched
224      } as ProbeAttrs),
225      m(Probe, {
226        title: 'CPU frequency and idle states',
227        img: 'rec_cpu_freq.png',
228        descr: 'Records cpu frequency and idle state changes via ftrace',
229        setEnabled: (cfg, val) => cfg.cpuFreq = val,
230        isEnabled: (cfg) => cfg.cpuFreq
231      } as ProbeAttrs),
232      m(Probe, {
233        title: 'Scheduling chains / latency analysis',
234        img: 'rec_cpu_wakeup.png',
235        descr: `Tracks causality of scheduling transitions. When a task
236                X transitions from blocked -> runnable, keeps track of the
237                task Y that X's transition (e.g. posting a semaphore).`,
238        setEnabled: (cfg, val) => cfg.cpuLatency = val,
239        isEnabled: (cfg) => cfg.cpuLatency
240      } as ProbeAttrs));
241}
242
243function MemorySettings(cssClass: string) {
244  const meminfoOpts = new Map<string, string>();
245  for (const x in MeminfoCounters) {
246    if (typeof MeminfoCounters[x] === 'number' &&
247        !`${x}`.endsWith('_UNSPECIFIED')) {
248      meminfoOpts.set(x, x.replace('MEMINFO_', '').toLowerCase());
249    }
250  }
251  const vmstatOpts = new Map<string, string>();
252  for (const x in VmstatCounters) {
253    if (typeof VmstatCounters[x] === 'number' &&
254        !`${x}`.endsWith('_UNSPECIFIED')) {
255      vmstatOpts.set(x, x.replace('VMSTAT_', '').toLowerCase());
256    }
257  }
258  return m(
259      `.record-section${cssClass}`,
260      m(Probe,
261        {
262          title: 'Kernel meminfo',
263          img: 'rec_meminfo.png',
264          descr: 'Polling of /proc/meminfo',
265          setEnabled: (cfg, val) => cfg.meminfo = val,
266          isEnabled: (cfg) => cfg.meminfo
267        } as ProbeAttrs,
268        m(Slider, {
269          title: 'Poll rate',
270          cssClass: '.thin',
271          values: POLL_RATE_MS,
272          unit: 'ms',
273          set: (cfg, val) => cfg.meminfoPeriodMs = val,
274          get: (cfg) => cfg.meminfoPeriodMs
275        } as SliderAttrs),
276        m(Dropdown, {
277          title: 'Select counters',
278          cssClass: '.multicolumn',
279          options: meminfoOpts,
280          set: (cfg, val) => cfg.meminfoCounters = val,
281          get: (cfg) => cfg.meminfoCounters
282        } as DropdownAttrs)),
283      m(Probe, {
284        title: 'High-frequency memory events',
285        img: 'rec_mem_hifreq.png',
286        descr: `Allows to track short memory spikes and transitories through
287                ftrace's mm_event, rss_stat and ion events. Avialable only
288                on recent Android Q+ kernels`,
289        setEnabled: (cfg, val) => cfg.memHiFreq = val,
290        isEnabled: (cfg) => cfg.memHiFreq
291      } as ProbeAttrs),
292      m(Probe, {
293        title: 'Low memory killer',
294        img: 'rec_lmk.png',
295        descr: `Record LMK events. Works both with the old in-kernel LMK
296                and the newer userspace lmkd. It also tracks OOM score
297                adjustments.`,
298        setEnabled: (cfg, val) => cfg.memLmk = val,
299        isEnabled: (cfg) => cfg.memLmk
300      } as ProbeAttrs),
301      m(Probe,
302        {
303          title: 'Per process stats',
304          img: 'rec_ps_stats.png',
305          descr: `Periodically samples all processes in the system tracking:
306                    their thread list, memory counters (RSS, swap and other
307                    /proc/status counters) and oom_score_adj.`,
308          setEnabled: (cfg, val) => cfg.procStats = val,
309          isEnabled: (cfg) => cfg.procStats
310        } as ProbeAttrs,
311        m(Slider, {
312          title: 'Poll rate',
313          cssClass: '.thin',
314          values: POLL_RATE_MS,
315          unit: 'ms',
316          set: (cfg, val) => cfg.procStatsPeriodMs = val,
317          get: (cfg) => cfg.procStatsPeriodMs
318        } as SliderAttrs)),
319      m(Probe,
320        {
321          title: 'Virtual memory stats',
322          img: 'rec_vmstat.png',
323          descr: `Periodically polls virtual memory stats from /proc/vmstat.
324                    Allows to gather statistics about swap, eviction,
325                    compression and pagecache efficiency`,
326          setEnabled: (cfg, val) => cfg.vmstat = val,
327          isEnabled: (cfg) => cfg.vmstat
328        } as ProbeAttrs,
329        m(Slider, {
330          title: 'Poll rate',
331          cssClass: '.thin',
332          values: POLL_RATE_MS,
333          unit: 'ms',
334          set: (cfg, val) => cfg.vmstatPeriodMs = val,
335          get: (cfg) => cfg.vmstatPeriodMs
336        } as SliderAttrs),
337        m(Dropdown, {
338          title: 'Select counters',
339          cssClass: '.multicolumn',
340          options: vmstatOpts,
341          set: (cfg, val) => cfg.vmstatCounters = val,
342          get: (cfg) => cfg.vmstatCounters
343        } as DropdownAttrs)));
344}
345
346
347function AndroidSettings(cssClass: string) {
348  return m(
349      `.record-section${cssClass}`,
350      m(Probe,
351        {
352          title: 'Atrace userspace annotations',
353          img: 'rec_atrace.png',
354          descr: `Enables C++ / Java codebase annotations (ATRACE_BEGIN() /
355                    os.Trace())`,
356          setEnabled: (cfg, val) => cfg.atrace = val,
357          isEnabled: (cfg) => cfg.atrace
358        } as ProbeAttrs,
359        m(Dropdown, {
360          title: 'Categories',
361          cssClass: '.multicolumn.atrace-categories',
362          options: ATRACE_CATEGORIES,
363          set: (cfg, val) => cfg.atraceCats = val,
364          get: (cfg) => cfg.atraceCats
365        } as DropdownAttrs),
366        m(Textarea, {
367          placeholder: 'Extra apps to profile, one per line, e.g.:\n' +
368              'com.android.phone\n' +
369              'com.android.nfc',
370          set: (cfg, val) => cfg.atraceApps = val,
371          get: (cfg) => cfg.atraceApps
372        } as TextareaAttrs)),
373      m(Probe,
374        {
375          title: 'Event log (logcat)',
376          img: 'rec_logcat.png',
377          descr: `Streams the event log into the trace. If no buffer filter is
378                    specified, all buffers are selected.`,
379          setEnabled: (cfg, val) => cfg.androidLogs = val,
380          isEnabled: (cfg) => cfg.androidLogs
381        } as ProbeAttrs,
382        m(Dropdown, {
383          title: 'Buffers',
384          options: LOG_BUFFERS,
385          set: (cfg, val) => cfg.androidLogBuffers = val,
386          get: (cfg) => cfg.androidLogBuffers
387        } as DropdownAttrs), ));
388}
389
390
391function AdvancedSettings(cssClass: string) {
392  return m(
393      `.record-section${cssClass}`,
394      m(Probe,
395        {
396          title: 'Advanced ftrace config',
397          img: 'rec_ftrace.png',
398          descr: `Tunes the kernel-tracing (ftrace) module and allows to
399                    enable extra events. The events enabled here are on top
400                    of the ones derived when enabling the other probes.`,
401          setEnabled: (cfg, val) => cfg.ftrace = val,
402          isEnabled: (cfg) => cfg.ftrace
403        } as ProbeAttrs,
404        m(Slider, {
405          title: 'Buf size',
406          cssClass: '.thin',
407          values: [512, 1024, 2 * 1024, 4 * 1024, 16 * 1024, 32 * 1024],
408          unit: 'KB',
409          set: (cfg, val) => cfg.ftraceBufferSizeKb = val,
410          get: (cfg) => cfg.ftraceBufferSizeKb
411        } as SliderAttrs),
412        m(Slider, {
413          title: 'Drain rate',
414          cssClass: '.thin',
415          values: [100, 250, 500, 1000, 2500, 5000],
416          unit: 'ms',
417          set: (cfg, val) => cfg.ftraceDrainPeriodMs = val,
418          get: (cfg) => cfg.ftraceDrainPeriodMs
419        } as SliderAttrs),
420        m(Dropdown, {
421          title: 'Event groups',
422          cssClass: '.multicolumn.ftrace-events',
423          options: FTRACE_CATEGORIES,
424          set: (cfg, val) => cfg.ftraceEvents = val,
425          get: (cfg) => cfg.ftraceEvents
426        } as DropdownAttrs),
427        m(Textarea, {
428          placeholder: 'Add extra events, one per line, e.g.:\n' +
429              'sched/sched_switch\n' +
430              'kmem/*',
431          set: (cfg, val) => cfg.ftraceExtraEvents = val,
432          get: (cfg) => cfg.ftraceExtraEvents
433        } as TextareaAttrs)));
434}
435
436function Instructions(cssClass: string) {
437  const data = globals.trackDataStore.get('config') as {
438    commandline: string,
439    pbtxt: string,
440  } | null;
441
442  const pbtx = data ? data.pbtxt : '';
443  let cmd = '';
444  cmd += 'adb shell perfetto \\\n';
445  cmd += '  -c - --txt \\\n';
446  cmd += '  -o /data/misc/perfetto-traces/trace \\\n';
447  cmd += '<<EOF\n\n';
448  cmd += pbtx;
449  cmd += '\nEOF\n';
450  const docUrl = '//docs.perfetto.dev/#/build-instructions?id=get-the-code';
451
452
453  const notes: m.Children = [];
454  const doc =
455      m('span', 'Follow the ', m('a', {href: docUrl}, 'instructions here'));
456
457  const msgFeatNotSupported =
458      m('div', `Some of the probes are only supported in the
459      last version of perfetto running on Android Q+`);
460
461  const msgPerfettoNotSupported =
462      m('div', `Perfetto is not supported natively before Android P.`);
463
464  const msgSideload =
465      m('div',
466        `If you have a rooted device you can sideload the latest version of
467         perfetto. `,
468        doc);
469
470  const msgLinux =
471      m('div', `In order to use perfetto on Linux you need to
472      compile it and run from the standalone build. `, doc);
473
474  switch (globals.state.recordConfig.targetOS) {
475    case 'Q':
476      break;
477    case 'P':
478      notes.push(msgFeatNotSupported);
479      notes.push(msgSideload);
480      break;
481    case 'O':
482      notes.push(msgPerfettoNotSupported);
483      notes.push(msgSideload);
484      break;
485    case 'L':
486      notes.push(msgLinux);
487      break;
488    default:
489  }
490
491  const onOsChange = (os: string) => {
492    const traceCfg = produce(globals.state.recordConfig, draft => {
493      draft.targetOS = os;
494    });
495    globals.dispatch(Actions.setRecordConfig({config: traceCfg}));
496  };
497
498  return m(
499      `.record-section.instructions${cssClass}`,
500      m('header', 'Instructions'),
501      m('label',
502        'Select target platform',
503        m('select',
504          {onchange: m.withAttr('value', onOsChange)},
505          m('option', {value: 'Q'}, 'Android Q+'),
506          m('option', {value: 'P'}, 'Android P'),
507          m('option', {value: 'O'}, 'Android O-'),
508          m('option', {value: 'L'}, 'Linux desktop'))),
509      notes.length > 0 ? m('.note', notes) : [],
510      m(CodeSnippet, {text: cmd, hardWhitespace: true}), );
511}
512
513export const RecordPage = createPage({
514  view() {
515    const SECTIONS: {[property: string]: (cssClass: string) => m.Child} = {
516      buffers: RecSettings,
517      instructions: Instructions,
518      cpu: CpuSettings,
519      power: PowerSettings,
520      memory: MemorySettings,
521      android: AndroidSettings,
522      advanced: AdvancedSettings,
523    };
524
525    const pages: m.Children = [];
526    let routePage = Router.param('p');
527    if (!Object.keys(SECTIONS).includes(routePage)) {
528      routePage = 'buffers';
529    }
530    for (const key of Object.keys(SECTIONS)) {
531      const cssClass = routePage === key ? '.active' : '';
532      pages.push(SECTIONS[key](cssClass));
533    }
534
535    return m(
536        '.record-page',
537        m('.record-container',
538          m('.record-menu',
539            m('header', 'Trace config'),
540            m('ul',
541              m('a[href="#!/record?p=buffers"]',
542                m(`li${routePage === 'buffers' ? '.active' : ''}`,
543                  m('i.material-icons', 'tune'),
544                  m('.title', 'Recording settings'),
545                  m('.sub', 'Buffer mode, size and duration'))),
546              m('a[href="#!/record?p=instructions"]',
547                m(`li${routePage === 'instructions' ? '.active' : ''}`,
548                  m('i.material-icons.rec', 'fiber_manual_record'),
549                  m('.title', 'Start recording'),
550                  m('.sub', 'Generate config and instructions'))), ),
551            m('header', 'Probes'),
552            m('ul',
553              m('a[href="#!/record?p=cpu"]',
554                m(`li${routePage === 'cpu' ? '.active' : ''}`,
555                  m('i.material-icons', 'subtitles'),
556                  m('.title', 'CPU'),
557                  m('.sub', 'CPU usage, scheduling, wakeups'))),
558              m('a[href="#!/record?p=power"]',
559                m(`li${routePage === 'power' ? '.active' : ''}`,
560                  m('i.material-icons', 'battery_charging_full'),
561                  m('.title', 'Power'),
562                  m('.sub', 'Battery and other energy counters'))),
563              m('a[href="#!/record?p=memory"]',
564                m(`li${routePage === 'memory' ? '.active' : ''}`,
565                  m('i.material-icons', 'memory'),
566                  m('.title', 'Memory'),
567                  m('.sub', 'Physical mem, VM, LMK'))),
568              m('a[href="#!/record?p=android"]',
569                m(`li${routePage === 'android' ? '.active' : ''}`,
570                  m('i.material-icons', 'android'),
571                  m('.title', 'Android apps & svcs'),
572                  m('.sub', 'atrace and logcat'))),
573              m('a[href="#!/record?p=advanced"]',
574                m(`li${routePage === 'advanced' ? '.active' : ''}`,
575                  m('i.material-icons', 'settings'),
576                  m('.title', 'Advanced settings'),
577                  m('.sub', 'Complicated stuff for wizards'))), )),
578          pages));
579  }
580});
581