• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2024 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 m from 'mithril';
16import protos from '../../../protos';
17import {
18  RecordSubpage,
19  RecordProbe,
20  ProbeSetting,
21} from '../config/config_interfaces';
22import {TraceConfigBuilder} from '../config/trace_config_builder';
23import {Toggle} from './widgets/toggle';
24import {Section} from '../../../widgets/section';
25import {
26  MultiSelect,
27  MultiSelectDiff,
28  MultiSelectOption,
29} from '../../../widgets/multiselect';
30import {Result} from '../../../base/result';
31
32type ChromeCatFunction = () => Promise<Result<string[]>>;
33
34export function chromeRecordSection(
35  chromeCategoryGetter: ChromeCatFunction,
36): RecordSubpage {
37  return {
38    kind: 'PROBES_PAGE',
39    id: 'chrome',
40    title: 'Chrome browser',
41    subtitle: 'Chrome tracing',
42    icon: 'laptop_chromebook',
43    probes: [chromeProbe(chromeCategoryGetter)],
44  };
45}
46
47function chromeProbe(chromeCategoryGetter: ChromeCatFunction): RecordProbe {
48  const groupToggles = Object.fromEntries(
49    Object.keys(GROUPS).map((groupName) => [
50      groupName,
51      new Toggle({
52        title: groupName,
53      }),
54    ]),
55  );
56  const settings = {
57    ...groupToggles,
58    privacy: new Toggle({
59      title: 'Remove untyped and sensitive data like URLs from the trace',
60      descr:
61        'Not recommended unless you intend to share the trace' +
62        ' with third-parties.',
63    }),
64    categories: new ChromeCategoriesWidget(chromeCategoryGetter),
65  };
66  return {
67    id: 'chrome_tracing',
68    title: 'Chrome browser tracing',
69    settings,
70    genConfig: function (tc: TraceConfigBuilder) {
71      const cats = new Set<string>();
72      settings.categories.getEnabledCategories().forEach((c) => cats.add(c));
73      for (const [group, groupCats] of Object.entries(GROUPS)) {
74        if ((groupToggles[group] as Toggle).enabled) {
75          groupCats.forEach((c) => cats.add(c));
76        }
77      }
78      const memoryInfra = cats.has('disabled-by-default-memory-infra');
79      const jsonStruct = {
80        record_mode:
81          tc.mode === 'STOP_WHEN_FULL'
82            ? 'record-until-full'
83            : 'record-continuously',
84        included_categories: [...cats],
85        excluded_categories: ['*'], // Only include categories explicitly
86        memory_dump_config: memoryInfra
87          ? {
88              allowed_dump_modes: ['background', 'light', 'detailed'],
89              triggers: [
90                {
91                  min_time_between_dumps_ms: 10000,
92                  mode: 'detailed',
93                  type: 'periodic_interval',
94                },
95              ],
96            }
97          : undefined,
98      };
99      const privacyFilteringEnabled = settings.privacy.enabled;
100      const chromeConfig = {
101        clientPriority: protos.ChromeConfig.ClientPriority.USER_INITIATED,
102        privacyFilteringEnabled,
103        traceConfig: JSON.stringify(jsonStruct),
104      };
105
106      const trackEvent = tc.addDataSource('track_event');
107      trackEvent.chromeConfig = chromeConfig;
108      const trackEvtCfg = (trackEvent.trackEventConfig ??= {});
109      trackEvtCfg.disabledCategories ??= ['*'];
110      trackEvtCfg.enabledCategories ??= [];
111      trackEvtCfg.enabledCategories.push(...cats);
112      trackEvtCfg.enabledCategories.push('__metadata');
113      trackEvtCfg.enableThreadTimeSampling = true;
114      trackEvtCfg.timestampUnitMultiplier = 1000;
115      trackEvtCfg.filterDynamicEventNames = privacyFilteringEnabled;
116      trackEvtCfg.filterDebugAnnotations = privacyFilteringEnabled;
117
118      tc.addDataSource('org.chromium.trace_metadata').chromeConfig =
119        chromeConfig;
120
121      if (memoryInfra) {
122        tc.addDataSource('org.chromium.memory_instrumentation').chromeConfig =
123          chromeConfig;
124        tc.addDataSource('org.chromium.native_heap_profiler').chromeConfig =
125          chromeConfig;
126      }
127
128      if (
129        cats.has('disabled-by-default-cpu_profiler') ||
130        cats.has('disabled-by-default-cpu_profiler.debug')
131      ) {
132        tc.addDataSource('org.chromium.sampler_profiler').chromeConfig =
133          chromeConfig;
134      }
135      if (cats.has('disabled-by-default-system_metrics')) {
136        tc.addDataSource('org.chromium.system_metrics').chromeConfig =
137          chromeConfig;
138      }
139      if (cats.has('disabled-by-default-histogram_samples')) {
140        const histogram = tc.addDataSource('org.chromium.histogram_samples');
141        const histogramCfg = (histogram.chromiumHistogramSamples ??= {});
142        histogramCfg.filterHistogramNames = privacyFilteringEnabled;
143      }
144    },
145  };
146}
147
148const DISAB_PREFIX = 'disabled-by-default-';
149
150export class ChromeCategoriesWidget implements ProbeSetting {
151  private options = new Array<MultiSelectOption>();
152  private fetchedRuntimeCategories = false;
153
154  constructor(private chromeCategoryGetter: ChromeCatFunction) {
155    // Initialize first with the static list of builtin categories (in case
156    // something goes wrong with the extension).
157    this.initializeCategories(BUILTIN_CATEGORIES);
158  }
159
160  private async fetchRuntimeCategoriesIfNeeded() {
161    if (this.fetchedRuntimeCategories) return;
162    const runtimeCategories = await this.chromeCategoryGetter();
163    if (runtimeCategories.ok) {
164      this.initializeCategories(runtimeCategories.value);
165      m.redraw();
166    }
167    this.fetchedRuntimeCategories = true;
168  }
169
170  private initializeCategories(cats: string[]) {
171    this.options = cats
172      .map((cat) => ({
173        id: cat,
174        name: cat.replace(DISAB_PREFIX, ''),
175        checked: this.options.find((o) => o.id === cat)?.checked ?? false,
176      }))
177      .sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));
178  }
179
180  getEnabledCategories(): string[] {
181    return this.options.filter((o) => o.checked).map((o) => o.id);
182  }
183
184  setEnabled(cat: string, enabled: boolean) {
185    for (const option of this.options) {
186      if (option.id !== cat) continue;
187      option.checked = enabled;
188    }
189  }
190
191  serialize() {
192    return this.options.filter((o) => o.checked).map((o) => o.id);
193  }
194
195  deserialize(state: unknown): void {
196    if (Array.isArray(state) && state.every((x) => typeof x === 'string')) {
197      this.options.forEach((o) => (o.checked = false));
198      for (const key of state) {
199        const opt = this.options.find((o) => o.id === key);
200        if (opt !== undefined) opt.checked = true;
201      }
202    }
203  }
204
205  render() {
206    return m(
207      'div.chrome-categories',
208      {
209        // This shouldn't be necessary in most cases. It's only needed:
210        // 1. The first time the user installs the extension.
211        // 2. In rare cases if the extension fails to respond to the call in the
212        //    constructor, to deal with its flakiness.
213        oninit: () => this.fetchRuntimeCategoriesIfNeeded(),
214      },
215      m(
216        Section,
217        {title: 'Additional Categories'},
218        m(MultiSelect, {
219          options: this.options.filter((o) => !o.id.startsWith(DISAB_PREFIX)),
220          repeatCheckedItemsAtTop: false,
221          fixedSize: false,
222          onChange: (diffs: MultiSelectDiff[]) => {
223            diffs.forEach(({id, checked}) => this.setEnabled(id, checked));
224          },
225        }),
226      ),
227      m(
228        Section,
229        {title: 'High Overhead Categories'},
230        m(MultiSelect, {
231          options: this.options.filter((o) => o.id.startsWith(DISAB_PREFIX)),
232          repeatCheckedItemsAtTop: false,
233          fixedSize: false,
234          onChange: (diffs: MultiSelectDiff[]) => {
235            diffs.forEach(({id, checked}) => this.setEnabled(id, checked));
236          },
237        }),
238      ),
239    );
240  }
241}
242
243function defaultAndDisabled(category: string) {
244  return [category, 'disabled-by-default-' + category];
245}
246
247const GROUPS = {
248  'Task Scheduling': [
249    'toplevel',
250    'toplevel.flow',
251    'scheduler',
252    'sequence_manager',
253    'disabled-by-default-toplevel.flow',
254  ],
255  'IPC Flows': [
256    'toplevel',
257    'toplevel.flow',
258    'disabled-by-default-ipc.flow',
259    'mojom',
260  ],
261  'Javascript execution': ['toplevel', 'v8'],
262  'Web content rendering, layout and compositing': [
263    'toplevel',
264    'blink',
265    'cc',
266    'gpu',
267  ],
268  'UI rendering and surface compositing': [
269    'toplevel',
270    'cc',
271    'gpu',
272    'viz',
273    'ui',
274    'views',
275  ],
276  'Input events': [
277    'toplevel',
278    'benchmark',
279    'evdev',
280    'input',
281    'disabled-by-default-toplevel.flow',
282  ],
283  'Navigation and loading': [
284    'loading',
285    'net',
286    'netlog',
287    'navigation',
288    'browser',
289  ],
290  'Audio': [
291    'base',
292    ...defaultAndDisabled('audio'),
293    ...defaultAndDisabled('webaudio'),
294    ...defaultAndDisabled('webaudio.audionode'),
295    ...defaultAndDisabled('webrtc'),
296    ...defaultAndDisabled('audio-worklet'),
297    ...defaultAndDisabled('mediastream'),
298    ...defaultAndDisabled('v8.gc'),
299    ...defaultAndDisabled('toplevel'),
300    ...defaultAndDisabled('toplevel.flow'),
301    ...defaultAndDisabled('wakeup.flow'),
302    ...defaultAndDisabled('cpu_profiler'),
303    ...defaultAndDisabled('scheduler'),
304    ...defaultAndDisabled('p2p'),
305    ...defaultAndDisabled('net'),
306  ],
307  'Video': [
308    'base',
309    'gpu',
310    'gpu.capture',
311    'media',
312    'toplevel',
313    'toplevel.flow',
314    'scheduler',
315    'wakeup.flow',
316    'webrtc',
317    'disabled-by-default-video_and_image_capture',
318    'disabled-by-default-webrtc',
319  ],
320};
321
322// List of static Chrome categories, last updated at 2024-05-15 from HEAD of
323// Chromium's //base/trace_event/builtin_categories.h.
324const BUILTIN_CATEGORIES = [
325  'accessibility',
326  'AccountFetcherService',
327  'android.adpf',
328  'android.ui.jank',
329  'android_webview',
330  'android_webview.timeline',
331  'aogh',
332  'audio',
333  'base',
334  'benchmark',
335  'blink',
336  'blink.animations',
337  'blink.bindings',
338  'blink.console',
339  'blink.net',
340  'blink.resource',
341  'blink.user_timing',
342  'blink.worker',
343  'blink_style',
344  'Blob',
345  'browser',
346  'browsing_data',
347  'CacheStorage',
348  'Calculators',
349  'CameraStream',
350  'cppgc',
351  'camera',
352  'cast_app',
353  'cast_perf_test',
354  'cast.mdns',
355  'cast.mdns.socket',
356  'cast.stream',
357  'cc',
358  'cc.debug',
359  'cdp.perf',
360  'chromeos',
361  'cma',
362  'compositor',
363  'content',
364  'content_capture',
365  'interactions',
366  'delegated_ink_trails',
367  'device',
368  'devtools',
369  'devtools.contrast',
370  'devtools.timeline',
371  'disk_cache',
372  'download',
373  'download_service',
374  'drm',
375  'drmcursor',
376  'dwrite',
377  'DXVA_Decoding',
378  'evdev',
379  'event',
380  'event_latency',
381  'exo',
382  'extensions',
383  'explore_sites',
384  'FileSystem',
385  'file_system_provider',
386  'fledge',
387  'fonts',
388  'GAMEPAD',
389  'gpu',
390  'gpu.angle',
391  'gpu.angle.texture_metrics',
392  'gpu.capture',
393  'graphics.pipeline',
394  'headless',
395  'history',
396  'hwoverlays',
397  'identity',
398  'ime',
399  'IndexedDB',
400  'input',
401  'input.scrolling',
402  'io',
403  'ipc',
404  'Java',
405  'jni',
406  'jpeg',
407  'latency',
408  'latencyInfo',
409  'leveldb',
410  'loading',
411  'log',
412  'login',
413  'media',
414  'media_router',
415  'memory',
416  'midi',
417  'mojom',
418  'mus',
419  'native',
420  'navigation',
421  'navigation.debug',
422  'net',
423  'network.scheduler',
424  'netlog',
425  'offline_pages',
426  'omnibox',
427  'oobe',
428  'openscreen',
429  'ozone',
430  'partition_alloc',
431  'passwords',
432  'p2p',
433  'page-serialization',
434  'paint_preview',
435  'pepper',
436  'PlatformMalloc',
437  'power',
438  'ppapi',
439  'ppapi_proxy',
440  'print',
441  'raf_investigation',
442  'rail',
443  'renderer',
444  'renderer_host',
445  'renderer.scheduler',
446  'resources',
447  'RLZ',
448  'ServiceWorker',
449  'SiteEngagement',
450  'safe_browsing',
451  'scheduler',
452  'scheduler.long_tasks',
453  'screenlock_monitor',
454  'segmentation_platform',
455  'sequence_manager',
456  'service_manager',
457  'sharing',
458  'shell',
459  'shortcut_viewer',
460  'shutdown',
461  'skia',
462  'sql',
463  'stadia_media',
464  'stadia_rtc',
465  'startup',
466  'sync',
467  'system_apps',
468  'test_gpu',
469  'toplevel',
470  'toplevel.flow',
471  'ui',
472  'v8',
473  'v8.execute',
474  'v8.wasm',
475  'ValueStoreFrontend::Backend',
476  'views',
477  'views.frame',
478  'viz',
479  'vk',
480  'wakeup.flow',
481  'wayland',
482  'webaudio',
483  'webengine.fidl',
484  'weblayer',
485  'WebCore',
486  'webnn',
487  'webrtc',
488  'webrtc_stats',
489  'xr',
490  'disabled-by-default-android_view_hierarchy',
491  'disabled-by-default-animation-worklet',
492  'disabled-by-default-audio',
493  'disabled-by-default-audio.latency',
494  'disabled-by-default-audio-worklet',
495  'disabled-by-default-base',
496  'disabled-by-default-blink.debug',
497  'disabled-by-default-blink.debug.display_lock',
498  'disabled-by-default-blink.debug.layout',
499  'disabled-by-default-blink.debug.layout.trees',
500  'disabled-by-default-blink.feature_usage',
501  'disabled-by-default-blink.image_decoding',
502  'disabled-by-default-blink.invalidation',
503  'disabled-by-default-identifiability',
504  'disabled-by-default-identifiability.high_entropy_api',
505  'disabled-by-default-cc',
506  'disabled-by-default-cc.debug',
507  'disabled-by-default-cc.debug.cdp-perf',
508  'disabled-by-default-cc.debug.display_items',
509  'disabled-by-default-cc.debug.lcd_text',
510  'disabled-by-default-cc.debug.picture',
511  'disabled-by-default-cc.debug.scheduler',
512  'disabled-by-default-cc.debug.scheduler.frames',
513  'disabled-by-default-cc.debug.scheduler.now',
514  'disabled-by-default-content.verbose',
515  'disabled-by-default-cpu_profiler',
516  'disabled-by-default-cppgc',
517  'disabled-by-default-cpu_profiler.debug',
518  'disabled-by-default-devtools.screenshot',
519  'disabled-by-default-devtools.timeline',
520  'disabled-by-default-devtools.timeline.frame',
521  'disabled-by-default-devtools.timeline.inputs',
522  'disabled-by-default-devtools.timeline.invalidationTracking',
523  'disabled-by-default-devtools.timeline.layers',
524  'disabled-by-default-devtools.timeline.picture',
525  'disabled-by-default-devtools.timeline.stack',
526  'disabled-by-default-devtools.target-rundown',
527  'disabled-by-default-devtools.v8-source-rundown',
528  'disabled-by-default-devtools.v8-source-rundown-sources',
529  'disabled-by-default-file',
530  'disabled-by-default-fonts',
531  'disabled-by-default-gpu_cmd_queue',
532  'disabled-by-default-gpu.dawn',
533  'disabled-by-default-gpu.debug',
534  'disabled-by-default-gpu.decoder',
535  'disabled-by-default-gpu.device',
536  'disabled-by-default-gpu.graphite.dawn',
537  'disabled-by-default-gpu.service',
538  'disabled-by-default-gpu.vulkan.vma',
539  'disabled-by-default-histogram_samples',
540  'disabled-by-default-java-heap-profiler',
541  'disabled-by-default-layer-element',
542  'disabled-by-default-layout_shift.debug',
543  'disabled-by-default-lifecycles',
544  'disabled-by-default-loading',
545  'disabled-by-default-mediastream',
546  'disabled-by-default-memory-infra',
547  'disabled-by-default-memory-infra.v8.code_stats',
548  'disabled-by-default-mojom',
549  'disabled-by-default-net',
550  'disabled-by-default-network',
551  'disabled-by-default-paint-worklet',
552  'disabled-by-default-power',
553  'disabled-by-default-renderer.scheduler',
554  'disabled-by-default-renderer.scheduler.debug',
555  'disabled-by-default-sequence_manager',
556  'disabled-by-default-sequence_manager.debug',
557  'disabled-by-default-sequence_manager.verbose_snapshots',
558  'disabled-by-default-skia',
559  'disabled-by-default-skia.gpu',
560  'disabled-by-default-skia.gpu.cache',
561  'disabled-by-default-skia.shaders',
562  'disabled-by-default-skottie',
563  'disabled-by-default-SyncFileSystem',
564  'disabled-by-default-system_power',
565  'disabled-by-default-system_stats',
566  'disabled-by-default-thread_pool_diagnostics',
567  'disabled-by-default-toplevel.ipc',
568  'disabled-by-default-user_action_samples',
569  'disabled-by-default-v8.compile',
570  'disabled-by-default-v8.cpu_profiler',
571  'disabled-by-default-v8.gc',
572  'disabled-by-default-v8.gc_stats',
573  'disabled-by-default-v8.ic_stats',
574  'disabled-by-default-v8.inspector',
575  'disabled-by-default-v8.runtime',
576  'disabled-by-default-v8.runtime_stats',
577  'disabled-by-default-v8.runtime_stats_sampling',
578  'disabled-by-default-v8.stack_trace',
579  'disabled-by-default-v8.turbofan',
580  'disabled-by-default-v8.wasm.detailed',
581  'disabled-by-default-v8.wasm.turbofan',
582  'disabled-by-default-video_and_image_capture',
583  'disabled-by-default-display.framedisplayed',
584  'disabled-by-default-viz.gpu_composite_time',
585  'disabled-by-default-viz.debug.overlay_planes',
586  'disabled-by-default-viz.hit_testing_flow',
587  'disabled-by-default-viz.overdraw',
588  'disabled-by-default-viz.quads',
589  'disabled-by-default-viz.surface_id_flow',
590  'disabled-by-default-viz.surface_lifetime',
591  'disabled-by-default-viz.triangles',
592  'disabled-by-default-viz.visual_debugger',
593  'disabled-by-default-webaudio.audionode',
594  'disabled-by-default-webgpu',
595  'disabled-by-default-webnn',
596  'disabled-by-default-webrtc',
597  'disabled-by-default-worker.scheduler',
598  'disabled-by-default-xr.debug',
599];
600