• 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 {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  const classLoadingTracks: Array<Promise<TrackNode>> = [];
53
54  // Find app startups
55  let result = await trace.engine.query(
56    `
57        INCLUDE PERFETTO MODULE android.startup.startups;
58        SELECT startup_id AS id, package, ts, ts_end FROM android_startups;`,
59    tag,
60  );
61
62  const it = result.iter({id: NUM, package: STR, ts: LONG, ts_end: LONG});
63  for (; it.valid(); it.next()) {
64    startups.push({
65      id: it.id,
66      package: it.package,
67      ts: it.ts,
68      ts_end: it.ts_end,
69    });
70  }
71
72  if (startups.length === 0) {
73    // Nothing interesting to report.
74    return undefined;
75  }
76
77  for (const startup of startups) {
78    // For each startup id get the optimization status
79    result = await trace.engine.query(
80      `
81        INCLUDE PERFETTO MODULE android.startup.startups;
82        SELECT slice_name AS name FROM
83          android_slices_for_startup_and_slice_name(${startup.id}, 'location=* status=* filter=* reason=*');`,
84      tag,
85    );
86    const it = result.iter({name: STR});
87    for (; it.valid(); it.next()) {
88      const name = it.name;
89      const relevant = name.indexOf(startup.package) >= 0;
90      if (relevant) {
91        const matches = name.match(FILTER_PATTERN);
92        if (matches) {
93          const filter = matches[1];
94          startup.filter = filter;
95          startup.optimized = filter === 'speed-profile';
96        }
97      }
98    }
99    const childTrack = classLoadingTrack(trace, startup);
100    classLoadingTracks.push(childTrack);
101  }
102
103  // Create the optimizations track and also avoid re-querying for the data we already have.
104  const sqlSource = startups
105    .map((startup) => {
106      return `SELECT
107        ${startup.ts} AS ts,
108        ${startup.ts_end - startup.ts} AS dur,
109        '${buildName(startup)}' AS name,
110        '${buildDetails(startup)}' AS details
111      `;
112    })
113    .join('UNION ALL '); // The trailing space is important.
114
115  const uri = '/android_startups_optimization_status';
116  const title = 'Optimization Status';
117  const track = await createQuerySliceTrack({
118    trace: trace,
119    uri: uri,
120    data: {
121      sqlSource: sqlSource,
122      columns: ['ts', 'dur', 'name', 'details'],
123    },
124    argColumns: ['details'],
125  });
126  trace.tracks.registerTrack({
127    uri,
128    title,
129    track,
130  });
131  const trackNode = new TrackNode({title, uri});
132  for await (const classLoadingTrack of classLoadingTracks) {
133    trackNode.addChildLast(classLoadingTrack);
134  }
135  return trackNode;
136}
137
138async function classLoadingTrack(
139  trace: Trace,
140  startup: Startup,
141): Promise<TrackNode> {
142  const sqlSource = `
143    SELECT slice_ts as ts, slice_dur as dur, slice_name AS name FROM
144      android_class_loading_for_startup
145      WHERE startup_id = ${startup.id}
146  `;
147  const uri = `/android_startups/${startup.id}/classloading`;
148  const title = `Unoptimized Class Loading in (${startup.package})`;
149  const track = await createQuerySliceTrack({
150    trace: trace,
151    uri: uri,
152    data: {
153      sqlSource: sqlSource,
154      columns: ['ts', 'dur', 'name'],
155    },
156  });
157  trace.tracks.registerTrack({
158    uri,
159    title,
160    track,
161  });
162  return new TrackNode({title, uri});
163}
164
165function buildName(startup: Startup): string {
166  if (
167    !!startup.filter === false ||
168    startup.filter === 'verify' ||
169    startup.filter === 'speed'
170  ) {
171    return `Sub-optimal compilation state (${startup.filter})`;
172  } else if (startup.filter === 'speed-profile') {
173    return 'Ideal compilation state (speed-profile)';
174  } else {
175    return `Unknown compilation state (${startup.filter})`;
176  }
177}
178
179function buildDetails(startup: Startup): string {
180  if (startup.filter === 'verify' || !!startup.filter === false) {
181    return `No methods are precompiled, and class loading is unoptimized`;
182  } else if (startup.filter === 'speed') {
183    return 'Methods are all precompiled, and class loading is unoptimized';
184  } else if (startup.filter === 'speed-profile') {
185    return 'Methods and classes in the profile are optimized';
186  } else {
187    return `Unknown compilation state (${startup.filter})`;
188  }
189}
190