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