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