xref: /aosp_15_r20/external/perfetto/ui/src/plugins/dev.perfetto.AndroidStartup/optimizations.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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 {Trace} from '../../public/trace';
16import {STR, LONG, NUM} from '../../trace_processor/query_result';
17import {createQuerySliceTrack} from '../../components/tracks/query_slice_track';
18import {TrackNode} from '../../public/workspace';
19
20// The metadata container that keeps track of optimizations for packages that have startup events.
21interface Startup {
22  // The startup id.
23  id: number;
24  // The package name.
25  package: string;
26  // Time start
27  ts: bigint;
28  // Time end
29  ts_end: bigint;
30  // compilation filter
31  filter?: string;
32  // optimization status
33  optimized?: boolean;
34}
35
36// The log tag
37const tag = 'DexOptInsights';
38// The pattern for the optimization filter.
39const FILTER_PATTERN = /filter=([^\s]+)/;
40
41/**
42 * Returns a track node that contains optimization status
43 * for the packages that started up in a trace.
44 * @param trace The loaded trace.
45 * @returns a track node with the optimizations status.
46 * `undefined` if there are no app startups detected.
47 */
48export async function optimizationsTrack(
49  trace: Trace,
50): Promise<TrackNode | undefined> {
51  const startups: Array<Startup> = [];
52
53  // Find app startups
54  let result = await trace.engine.query(
55    `
56        INCLUDE PERFETTO MODULE android.startup.startups;
57        SELECT startup_id AS id, package, ts, ts_end FROM android_startups;`,
58    tag,
59  );
60
61  const it = result.iter({id: NUM, package: STR, ts: LONG, ts_end: LONG});
62  for (; it.valid(); it.next()) {
63    startups.push({
64      id: it.id,
65      package: it.package,
66      ts: it.ts,
67      ts_end: it.ts_end,
68    });
69  }
70
71  if (startups.length === 0) {
72    // Nothing interesting to report.
73    return undefined;
74  }
75
76  for (const startup of startups) {
77    // For each startup id get the optimization status
78    result = await trace.engine.query(
79      `
80        INCLUDE PERFETTO MODULE android.startup.startups;
81        SELECT slice_name AS name FROM
82          android_slices_for_startup_and_slice_name(${startup.id}, 'location=* status=* filter=* reason=*');`,
83      tag,
84    );
85    const it = result.iter({name: STR});
86    for (; it.valid(); it.next()) {
87      const name = it.name;
88      const relevant = name.indexOf(startup.package) >= 0;
89      if (relevant) {
90        const matches = name.match(FILTER_PATTERN);
91        if (matches) {
92          const filter = matches[1];
93          startup.filter = filter;
94          startup.optimized = filter === 'speed-profile';
95        }
96      }
97    }
98  }
99
100  // Create the optimizations track and also avoid re-querying for the data we already have.
101  const sqlSource = startups
102    .map((startup) => {
103      return `SELECT
104        ${startup.ts} AS ts,
105        ${startup.ts_end - startup.ts} AS dur,
106        '${buildName(startup)}' AS name,
107        '${buildDetails(startup)}' AS details
108      `;
109    })
110    .join('UNION ALL '); // The trailing space is important.
111
112  const uri = '/android_startups_optimization_status';
113  const title = 'Optimization Status';
114  const track = await createQuerySliceTrack({
115    trace: trace,
116    uri: uri,
117    data: {
118      sqlSource: sqlSource,
119      columns: ['ts', 'dur', 'name', 'details'],
120    },
121    argColumns: ['details'],
122  });
123  trace.tracks.registerTrack({
124    uri,
125    title,
126    track,
127  });
128  return new TrackNode({title, uri});
129}
130
131function buildName(startup: Startup): string {
132  if (
133    !!startup.filter === false ||
134    startup.filter === 'verify' ||
135    startup.filter === 'speed'
136  ) {
137    return `Sub-optimal compilation state (${startup.filter})`;
138  } else if (startup.filter === 'speed-profile') {
139    return 'Ideal compilation state (speed-profile)';
140  } else {
141    return `Unknown compilation state (${startup.filter})`;
142  }
143}
144
145function buildDetails(startup: Startup): string {
146  if (startup.filter === 'verify' || !!startup.filter === false) {
147    return `No methods are precompiled, and class loading is unoptimized`;
148  } else if (startup.filter === 'speed') {
149    return 'Methods are all precompiled, and class loading is unoptimized';
150  } else if (startup.filter === 'speed-profile') {
151    return 'Methods and classes in the profile are optimized';
152  } else {
153    return `Unknown compilation state (${startup.filter})`;
154  }
155}
156