| // 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 {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, |
| }); |
| } |