| // Copyright (C) 2024 The Android Open Source Project | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| import type {Trace} from '../../public/trace'; | |
| import {STR, LONG, NUM} from '../../trace_processor/query_result'; | |
| import {TrackNode} from '../../public/workspace'; | |
| import {SliceTrack} from '../../components/tracks/slice_track'; | |
| import {SourceDataset} from '../../trace_processor/dataset'; | |
| import {DebugSliceTrackDetailsPanel} from '../../components/tracks/debug_slice_track_details_panel'; | |
| /** | |
| * Returns a track node that contains optimization status | |
| * for the packages that started up in a trace. | |
| * @param trace The loaded trace. | |
| * @returns a track node with the optimizations status. | |
| * `undefined` if there are no app startups detected. | |
| */ | |
| export async function optimizationsTrack( | |
| trace: Trace, | |
| ): Promise<TrackNode | undefined> { | |
| const startupsResult = await trace.engine.query( | |
| ` | |
| INCLUDE PERFETTO MODULE android.startup.startups; | |
| SELECT startup_id as id, package FROM android_startups; | |
| `, | |
| ); | |
| // Nothing interesting to report. | |
| if (startupsResult.numRows() === 0) { | |
| return undefined; | |
| } | |
| const classLoadingTracks: Array<TrackNode> = []; | |
| const it = startupsResult.iter({id: NUM, package: STR}); | |
| for (; it.valid(); it.next()) { | |
| const childTrack = classLoadingTrack(trace, { | |
| id: it.id, | |
| package: it.package, | |
| }); | |
| classLoadingTracks.push(childTrack); | |
| } | |
| await trace.engine.query( | |
| ` | |
| CREATE PERFETTO FUNCTION _startup_compilation_state(filter STRING) | |
| RETURNS STRING | |
| AS | |
| SELECT CASE | |
| WHEN $filter IN ('verify', 'speed') OR $filter IS NULL | |
| THEN FORMAT('Sub-optimal compilation state (%s)', ifnull($filter, 'unknown')) | |
| WHEN $filter = 'speed-profile' | |
| THEN 'Ideal compilation state (speed-profile)' | |
| ELSE | |
| FORMAT('Unknown compilation state (%s)', $filter) | |
| END; | |
| CREATE PERFETTO FUNCTION _startup_compilation_state_details(filter STRING) | |
| RETURNS STRING | |
| AS | |
| SELECT CASE | |
| WHEN $filter = 'verify' or $filter IS NULL | |
| THEN 'No methods are precompiled, and class loading is unoptimized' | |
| WHEN $filter = 'speed' | |
| THEN 'Methods are all precompiled, and class loading is unoptimized' | |
| WHEN $filter = 'speed-profile' | |
| THEN 'Methods and classes in the profile are optimized' | |
| ELSE | |
| FORMAT('Unknown compilation state (%s)', $filter) | |
| END; | |
| CREATE PERFETTO FUNCTION _startup_filter_extraction(startup_id INT) | |
| RETURNS TABLE(compile_ts LONG, filter STRING) | |
| AS | |
| SELECT | |
| MAX(slice_ts) AS compile_ts, | |
| regexp_extract(slice_name, 'filter=([^\\s]+)') as filter | |
| FROM android_thread_slices_for_all_startups | |
| WHERE slice_name GLOB 'location=* status=* filter=* reason=*' | |
| AND startup_id = $startup_id; | |
| CREATE PERFETTO TABLE _startup_optimization_slices AS | |
| SELECT | |
| s.ts, | |
| s.ts_end - s.ts as dur, | |
| s.startup_id as id, | |
| _startup_compilation_state(f.filter) AS name, | |
| _startup_compilation_state_details(f.filter) AS raw_details | |
| FROM android_startups s | |
| LEFT JOIN _startup_filter_extraction(s.startup_id) f | |
| `, | |
| ); | |
| const uri = '/android_startups_optimization_status'; | |
| const tableName = `_startup_optimization_slices`; | |
| trace.tracks.registerTrack({ | |
| uri, | |
| renderer: SliceTrack.create({ | |
| trace: trace, | |
| uri, | |
| dataset: new SourceDataset({ | |
| src: tableName, | |
| schema: { | |
| id: NUM, | |
| ts: LONG, | |
| dur: LONG, | |
| name: STR, | |
| raw_details: STR, | |
| }, | |
| }), | |
| detailsPanel: (row) => { | |
| return new DebugSliceTrackDetailsPanel(trace, tableName, row.id); | |
| }, | |
| }), | |
| }); | |
| const trackNode = new TrackNode({name: 'Optimization Status', uri}); | |
| for (const classLoadingTrack of classLoadingTracks) { | |
| trackNode.addChildLast(classLoadingTrack); | |
| } | |
| return trackNode; | |
| } | |
| function classLoadingTrack( | |
| trace: Trace, | |
| startup: {id: number; package: string}, | |
| ): TrackNode { | |
| const uri = `/android_startups/${startup.id}/classloading`; | |
| trace.tracks.registerTrack({ | |
| uri, | |
| renderer: SliceTrack.create({ | |
| trace, | |
| uri, | |
| dataset: new SourceDataset({ | |
| src: ` | |
| SELECT | |
| slice_ts as ts, | |
| slice_dur as dur, | |
| slice_name AS name, | |
| slice_id as id | |
| FROM android_class_loading_for_startup | |
| `, | |
| schema: { | |
| id: NUM, | |
| ts: LONG, | |
| dur: LONG, | |
| name: STR, | |
| }, | |
| filter: { | |
| col: 'id', | |
| eq: startup.id, | |
| }, | |
| }), | |
| }), | |
| }); | |
| return new TrackNode({ | |
| name: `Unoptimized Class Loading in (${startup.package})`, | |
| uri, | |
| }); | |
| } |