blob: 9d3a717a618ac4ec2f506a622757c20e30582c6b [file] [log] [blame] [edit]
// 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,
});
}