• 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 {splitLinesNonEmpty} from '../../../base/string_utils';
16import protos from '../../../protos';
17import {RecordSubpage, RecordProbe} from '../config/config_interfaces';
18import {TraceConfigBuilder} from '../config/trace_config_builder';
19import {TypedMultiselect} from './widgets/multiselect';
20import {POLL_INTERVAL_SLIDER, Slider} from './widgets/slider';
21import {Textarea} from './widgets/textarea';
22import {Toggle} from './widgets/toggle';
23
24export function androidRecordSection(): RecordSubpage {
25  return {
26    kind: 'PROBES_PAGE',
27    id: 'android',
28    title: 'Android apps & svcs',
29    subtitle: 'Android-specific data sources',
30    icon: 'android',
31    probes: [
32      atrace(),
33      logcat(),
34      frameTimeline(),
35      gameInterventions(),
36      netTracing(),
37      statsdAtoms(),
38    ],
39  };
40}
41
42function atrace(): RecordProbe {
43  const settings = {
44    categories: new TypedMultiselect<string>({
45      options: new Map(
46        Object.entries(ATRACE_CATEGORIES).map(([id, name]) => [
47          `${id}: ${name}`,
48          id,
49        ]),
50      ),
51    }),
52    apps: new Textarea({
53      title: 'Process / package names to trace',
54      placeholder: 'e.g. system_server\ncom.android.settings',
55    }),
56    allApps: new Toggle({
57      title: 'Record events from all Android apps and services',
58      cssClass: '.thin',
59      onChange(allAppsEnabled: boolean) {
60        settings.apps.attrs.disabled = allAppsEnabled;
61      },
62    }),
63  };
64  return {
65    id: 'atrace',
66    title: 'Atrace userspace annotations',
67    image: 'rec_atrace.png',
68    description:
69      'Enables C++ / Java codebase annotations (ATRACE_BEGIN() / os.Trace())',
70    supportedPlatforms: ['ANDROID'],
71    settings,
72    genConfig: function (tc: TraceConfigBuilder) {
73      tc.addAtraceCategories(...settings.categories.selectedValues());
74      if (settings.allApps.enabled) {
75        tc.addAtraceApps('*');
76      } else {
77        for (const line of splitLinesNonEmpty(settings.apps.text)) {
78          tc.addAtraceApps(line);
79        }
80      }
81      if (
82        settings.categories.selectedKeys().length > 0 ||
83        settings.allApps.enabled
84      ) {
85        tc.addFtraceEvents('ftrace/print');
86      }
87    },
88  };
89}
90
91function logcat(): RecordProbe {
92  const settings = {
93    buffers: new TypedMultiselect<protos.AndroidLogId>({
94      options: new Map(
95        Object.entries({
96          'Crash': protos.AndroidLogId.LID_CRASH,
97          'Main': protos.AndroidLogId.LID_DEFAULT,
98          'Binary events': protos.AndroidLogId.LID_EVENTS,
99          'Kernel': protos.AndroidLogId.LID_KERNEL,
100          'Radio': protos.AndroidLogId.LID_RADIO,
101          'Security': protos.AndroidLogId.LID_SECURITY,
102          'Stats': protos.AndroidLogId.LID_STATS,
103          'System': protos.AndroidLogId.LID_SYSTEM,
104        }),
105      ),
106    }),
107  };
108  return {
109    id: 'logcat',
110    title: 'Event log (logcat)',
111    image: 'rec_logcat.png',
112    description:
113      'Streams the event log into the trace. If no buffer filter is ' +
114      'specified, all buffers are selected.',
115    supportedPlatforms: ['ANDROID'],
116    settings,
117    genConfig: function (tc: TraceConfigBuilder) {
118      const logIds = settings.buffers.selectedValues();
119      tc.addDataSource('android.log').androidLogConfig = {
120        logIds: logIds.length > 0 ? logIds : undefined,
121      };
122    },
123  };
124}
125
126function frameTimeline(): RecordProbe {
127  return {
128    id: 'android_frame_timeline',
129    title: 'Frame timeline',
130    description:
131      'Records expected/actual frame timings from surface_flinger.' +
132      'Requires Android 12 (S) or above.',
133    supportedPlatforms: ['ANDROID'],
134    docsLink: 'https://perfetto.dev/docs/data-sources/frametimeline',
135    genConfig: function (tc: TraceConfigBuilder) {
136      tc.addDataSource('android.surfaceflinger.frametimeline');
137    },
138  };
139}
140
141function gameInterventions(): RecordProbe {
142  return {
143    id: 'android_game_interventions',
144    title: 'Game intervention list',
145    description:
146      'List game modes and interventions. Requires Android 13 (T) or above.',
147    supportedPlatforms: ['ANDROID'],
148    docsLink:
149      'https://perfetto.dev/docs/data-sources/android-game-intervention-list',
150    genConfig: function (tc: TraceConfigBuilder) {
151      tc.addDataSource('android.game_interventions');
152    },
153  };
154}
155
156function netTracing(): RecordProbe {
157  const settings = {pollMs: new Slider(POLL_INTERVAL_SLIDER)};
158  return {
159    id: 'network_tracing',
160    title: 'Network Tracing',
161    description:
162      'Records detailed information on network packets. ' +
163      'Requires Android 14 (U) or above',
164    supportedPlatforms: ['ANDROID'],
165    settings,
166    genConfig: function (tc: TraceConfigBuilder) {
167      tc.addDataSource('android.network_packets').networkPacketTraceConfig = {
168        pollMs: settings.pollMs.value,
169      };
170    },
171  };
172}
173
174function statsdAtoms(): RecordProbe {
175  const settings = {
176    pushAtoms: new TypedMultiselect<protos.AtomId>({
177      title: 'Push atoms',
178      options: new Map(
179        Object.entries(protos.AtomId)
180          .filter(([_, v]) => typeof v === 'number' && v > 2 && v < 9999)
181          .map(([k, v]) => [k, v as protos.AtomId]),
182      ),
183    }),
184    rawPushIds: new Textarea({
185      placeholder:
186        'Add raw pushed atoms IDs, one per line, e.g.:\n' + '818\n' + '819',
187    }),
188    pullAtoms: new TypedMultiselect<protos.AtomId>({
189      title: 'Pull atoms',
190      options: new Map(
191        Object.entries(protos.AtomId)
192          .filter(([_, v]) => typeof v === 'number' && v > 10000 && v < 99999)
193          .map(([k, v]) => [k, v as protos.AtomId]),
194      ),
195    }),
196    rawPullIds: new Textarea({
197      placeholder:
198        'Add raw pulled atom IDs, one per line, e.g.:\n10063\n10064\n',
199    }),
200    pullInterval: new Slider({...POLL_INTERVAL_SLIDER, default: 5000}),
201    pullPkg: new Textarea({
202      placeholder:
203        'Add pulled atom packages, one per line, e.g.:\n' +
204        'com.android.providers.telephony',
205    }),
206  };
207  return {
208    id: 'statsd',
209    title: 'Statsd atoms',
210    description: 'Record instances of statsd atoms to the Statsd Atoms track.',
211    supportedPlatforms: ['ANDROID'],
212    docsLink:
213      'https://cs.android.com/android/platform/superproject/main/+/main:frameworks/proto_logging/stats/atoms.proto',
214    settings,
215    genConfig: function (tc: TraceConfigBuilder) {
216      const pkg = splitLinesNonEmpty(settings.pullPkg.text);
217      const pullIds = settings.pullAtoms.selectedValues();
218      const rawPullIds = splitLinesNonEmpty(settings.rawPullIds.text).map((l) =>
219        parseInt(l.trim()),
220      );
221      const hasPull = pullIds.length > 0 || rawPullIds.length > 0;
222      tc.addDataSource('android.statsd').statsdTracingConfig = {
223        pushAtomId: settings.pushAtoms.selectedValues(),
224        rawPushAtomId: splitLinesNonEmpty(settings.rawPushIds.text).map((l) =>
225          parseInt(l.trim()),
226        ),
227        pullConfig: hasPull
228          ? [
229              {
230                pullAtomId: pullIds,
231                rawPullAtomId: rawPullIds,
232                pullFrequencyMs: settings.pullInterval.value,
233                packages: pkg.length > 0 ? pkg : undefined,
234              },
235            ]
236          : undefined,
237      };
238    },
239  };
240}
241
242const ATRACE_CATEGORIES = {
243  adb: 'ADB',
244  aidl: 'AIDL calls',
245  am: 'Activity Manager',
246  audio: 'Audio',
247  binder_driver: 'Binder Kernel driver',
248  binder_lock: 'Binder global lock trace',
249  bionic: 'Bionic C library',
250  camera: 'Camera',
251  dalvik: 'ART & Dalvik',
252  database: 'Database',
253  gfx: 'Graphics',
254  hal: 'Hardware Modules',
255  input: 'Input',
256  network: 'Network',
257  nnapi: 'Neural Network API',
258  pm: 'Package Manager',
259  power: 'Power Management',
260  res: 'Resource Loading',
261  rro: 'Resource Overlay',
262  rs: 'RenderScript',
263  sm: 'Sync Manager',
264  ss: 'System Server',
265  vibrator: 'Vibrator',
266  video: 'Video',
267  view: 'View System',
268  webview: 'WebView',
269  wm: 'Window Manager',
270};
271