Skip to content

Commit

Permalink
Add optimization status to the App Startup plugin.
Browse files Browse the repository at this point in the history
* Makes sure when looking at app startups, that the app has been compiled with the right ART compilation filter.

Change-Id: I6d3a15aef6fba2bf1129a408d4253a0a89f1fe1e
  • Loading branch information
tikurahul committed Nov 22, 2024
1 parent 57e9e9c commit 89decae
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 1 deletion.
8 changes: 7 additions & 1 deletion ui/src/plugins/dev.perfetto.AndroidStartup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ import {Trace} from '../../public/trace';
import {PerfettoPlugin} from '../../public/plugin';
import {createQuerySliceTrack} from '../../public/lib/tracks/query_slice_track';
import {TrackNode} from '../../public/workspace';
import {optimizationsTrack} from './optimizations';

export default class implements PerfettoPlugin {
static readonly id = 'dev.perfetto.AndroidStartup';

async onTraceLoad(ctx: Trace): Promise<void> {
const e = ctx.engine;
await e.query(`
Expand Down Expand Up @@ -55,9 +58,12 @@ export default class implements PerfettoPlugin {
`/android_startups_breakdown`,
'Android App Startups Breakdown',
);

const optimizations = await optimizationsTrack(ctx);
ctx.workspace.addChildInOrder(trackNode);
trackNode.addChildLast(trackBreakdownNode);
if (!!optimizations) {
trackNode.addChildLast(optimizations);
}
}

private async loadStartupTrack(
Expand Down
155 changes: 155 additions & 0 deletions ui/src/plugins/dev.perfetto.AndroidStartup/optimizations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// 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 {createQuerySliceTrack} from '../../public/lib/tracks/query_slice_track';
import {TrackNode} from '../../public/workspace';

// The metadata container that keeps track of optimizations for packages that have startup events.
interface Startup {
// The startup id.
id: number;
// The package name.
package: string;
// Time start
ts: bigint;
// Time end
ts_end: bigint;
// compilation filter
filter?: string;
// optimization status
optimized?: boolean;
}

// The log tag
const tag = 'DexOptInsights';
// The pattern for the optimization filter.
const FILTER_PATTERN = /filter=([^\s]+)/;

/**
* 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 startups: Array<Startup> = [];

// Find app startups
let result = await trace.engine.query(
`
INCLUDE PERFETTO MODULE android.startup.startups;
SELECT startup_id AS id, package, ts, ts_end FROM android_startups;`,
tag,
);

const it = result.iter({id: NUM, package: STR, ts: LONG, ts_end: LONG});
for (; it.valid(); it.next()) {
startups.push({
id: it.id,
package: it.package,
ts: it.ts,
ts_end: it.ts_end,
});
}

if (startups.length === 0) {
// Nothing interesting to report.
return undefined;
}

for (const startup of startups) {
// For each startup id get the optimization status
result = await trace.engine.query(
`
INCLUDE PERFETTO MODULE android.startup.startups;
SELECT slice_name AS name FROM
android_slices_for_startup_and_slice_name(${startup.id}, 'location=* status=* filter=* reason=*');`,
tag,
);
const it = result.iter({name: STR});
for (; it.valid(); it.next()) {
const name = it.name;
const relevant = name.indexOf(startup.package) >= 0;
if (relevant) {
const matches = name.match(FILTER_PATTERN);
if (matches) {
const filter = matches[1];
startup.filter = filter;
startup.optimized = filter === 'speed-profile';
}
}
}
}

// Create the optimizations track and also avoid re-querying for the data we already have.
const sqlSource = startups
.map((startup) => {
return `SELECT
${startup.ts} AS ts,
${startup.ts_end - startup.ts} AS dur,
'${buildName(startup)}' AS name,
'${buildDetails(startup)}' AS details
`;
})
.join('UNION ALL');

const uri = '/android_startups_optimization_status';
const title = 'Optimization Status';
const track = await createQuerySliceTrack({
trace: trace,
uri: uri,
data: {
sqlSource: sqlSource,
columns: ['ts', 'dur', 'name', 'details'],
},
argColumns: ['details'],
});
trace.tracks.registerTrack({
uri,
title,
track,
});
return new TrackNode({title, uri});
}

function buildName(startup: Startup): string {
if (
!!startup.filter === false ||
startup.filter === 'verify' ||
startup.filter === 'speed'
) {
return `Sub-optimal compilation state (${startup.filter})`;
} else if (startup.filter === 'speed-profile') {
return 'Ideal compilation state (speed-profile)';
} else {
return `Unknown compilation state (${startup.filter})`;
}
}

function buildDetails(startup: Startup): string {
if (startup.filter === 'verify' || !!startup.filter === false) {
return `No methods are precompiled, and class loading is unoptimized`;
} else if (startup.filter === 'speed') {
return 'Methods are all precompiled, and class loading is unoptimized';
} else if (startup.filter === 'speed-profile') {
return 'Methods and classes in the profile are optimized';
} else {
return `Unknown compilation state (${startup.filter})`;
}
}

0 comments on commit 89decae

Please sign in to comment.