blob: f447759c04b7e1c0686bbe3df8541115cec692a3 [file]
// Copyright (C) 2026 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 m from 'mithril';
import {Tab} from '../../public/tab';
import {Trace} from '../../public/trace';
import {DetailsShell} from '../../widgets/details_shell';
import {Section} from '../../widgets/section';
import {AndroidLockContentionEventSource} from './android_lock_contention_event_source';
import {Anchor} from '../../widgets/anchor';
import {Icons} from '../../base/semantic_icons';
import {Spinner} from '../../widgets/spinner';
import {time} from '../../base/time';
import {RelatedEvent} from '../dev.perfetto.RelatedEvents/interface';
interface LockContentionArgs {
short_blocked_method: string;
short_blocking_method: string;
blocking_thread_name: string;
blockingTrackUri: string;
blockingSliceId?: number;
allTrackUris?: string[];
}
function isLockContentionArgs(args: unknown): args is LockContentionArgs {
if (typeof args !== 'object' || args === null) return false;
const obj = args as Record<string, unknown>;
return (
typeof obj.short_blocked_method === 'string' &&
typeof obj.short_blocking_method === 'string'
);
}
interface LockContentionTabConfig {
trace: Trace;
source: AndroidLockContentionEventSource;
}
export class AndroidLockContentionTab implements Tab {
private event: RelatedEvent | null = null;
private isLoading = false;
constructor(private config: LockContentionTabConfig) {}
syncSelection() {
const selection = this.config.trace.selection.selection;
if (selection.kind === 'track_event') {
this.loadData(selection.eventId);
} else {
this.event = null;
}
}
async loadData(eventId: number) {
if (this.isLoading) return;
this.isLoading = true;
this.event = null;
try {
const data = await this.config.source.getRelatedEventData(eventId);
this.event = data.events.length > 0 ? data.events[0] : null;
} finally {
this.isLoading = false;
}
}
getTitle() {
return 'Lock Contention';
}
private goTo(trackUri: string, eventId: number) {
this.config.trace.selection.selectTrackEvent(trackUri, eventId, {
scrollToSelection: true,
switchToCurrentSelectionTab: false,
});
}
private scrollToTime(trackUri: string, ts: time) {
this.config.trace.scrollTo({
time: {
start: ts,
behavior: 'pan',
},
track: {
uri: trackUri,
expandGroup: true,
},
});
}
render(): m.Children {
if (this.isLoading) {
return m(DetailsShell, {title: this.getTitle()}, m(Spinner, {}));
}
if (!this.event || !this.event.customArgs) {
return m(
DetailsShell,
{title: this.getTitle()},
m('.note', 'Select a lock contention event.'),
);
}
const args = this.event.customArgs;
if (!isLockContentionArgs(args)) {
console.error('Invalid customArgs for LockContention event', args);
return m(
DetailsShell,
{title: this.getTitle()},
m('.note', 'Error: Invalid event data.'),
);
}
return m(
DetailsShell,
{title: this.getTitle()},
m(
Section,
{
title: 'Contention Details',
},
m('div', `Blocked Method: ${args.short_blocked_method}`),
m('div', `Blocking Method: ${args.short_blocking_method}`),
m(
Anchor,
{
icon: Icons.GoTo,
onclick: () => this.goTo(this.event!.trackUri, this.event!.id),
title: 'Go to Blocked Event',
},
'Go to Blocked',
),
args.blockingTrackUri &&
args.blockingSliceId !== undefined &&
m(
Anchor,
{
icon: Icons.GoTo,
onclick: () =>
this.goTo(args.blockingTrackUri!, args.blockingSliceId!),
title: 'Go to Blocking Slice',
},
'Go to Blocking Slice',
),
args.blockingTrackUri &&
args.blockingSliceId === undefined &&
m(
Anchor,
{
icon: Icons.GoTo,
onclick: () =>
this.scrollToTime(args.blockingTrackUri!, this.event!.ts),
title: 'Scroll to Blocking Thread at Contention Time',
},
'Scroll to Blocking Thread',
),
),
);
}
}