Skip to content

Commit 3d50189

Browse files
committed
ui: Improve com.android.AndroidLockContention UX
This CL improves the UX of navigation by creating custom tracks above each blocking thread to mirror the lock contetion slices. These act as a visual indicator of where the contention is on the thread and allows the relevant contention details to ne displayed in the current selection tab. This also gives a target for arrows to be drawn between the two contention slices as a direct visual indicator of the relation. Bug: 289343169
1 parent 0043673 commit 3d50189

4 files changed

Lines changed: 559 additions & 510 deletions

File tree

ui/src/plugins/com.android.AndroidLockContention/android_lock_contention_event_source.ts

Lines changed: 134 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,145 @@ export class AndroidLockContentionEventSource {
8484

8585
constructor(private readonly trace: Trace) {}
8686

87-
use(eventId: number): QueryResult<LockContentionDetails | null> {
87+
use(
88+
eventId: number,
89+
trackUri: string,
90+
): QueryResult<LockContentionDetails | null> {
8891
return this.dataSlot.use({
89-
key: eventId,
90-
queryFn: async () => this.fetchDetails(eventId),
92+
key: {eventId, trackUri},
93+
queryFn: async () => this.fetchDetails(eventId, trackUri),
9194
});
9295
}
9396

94-
private async fetchDetails(
97+
async fetchDetails(
9598
eventId: number,
99+
trackUri: string,
96100
): Promise<LockContentionDetails | null> {
101+
let resolvedId = eventId;
102+
const debugMatch = trackUri.match(/^debug\.track(\d+)(?:_\d+)?$/);
103+
104+
const ownerTrackPrefix = 'com.android.AndroidLockContention#OwnerEvents';
105+
if (trackUri.startsWith(ownerTrackPrefix)) {
106+
const query = await this.trace.engine.query(`
107+
SELECT original_id FROM __android_lock_contention_owner_events WHERE id = ${eventId} LIMIT 1
108+
`);
109+
if (query.numRows() > 0) {
110+
resolvedId = query.firstRow({original_id: NUM}).original_id;
111+
}
112+
} else if (debugMatch) {
113+
const tableId = debugMatch[1];
114+
const tableName = `__debug_track_${tableId}`;
115+
const query = await this.trace.engine.query(`
116+
SELECT raw_original_id FROM ${tableName} WHERE id = ${eventId} LIMIT 1
117+
`);
118+
if (query.numRows() > 0) {
119+
resolvedId = query.firstRow({raw_original_id: NUM}).raw_original_id;
120+
}
121+
}
122+
123+
const typeQuery = await this.trace.engine.query(`
124+
SELECT name, ts, dur FROM slice WHERE id = ${resolvedId} LIMIT 1
125+
`);
126+
if (typeQuery.numRows() === 0) return null;
127+
const typeRow = typeQuery.firstRow({name: STR, ts: LONG, dur: LONG});
128+
129+
const monitorQuery = await this.trace.engine.query(`
130+
SELECT 1 FROM android_monitor_contention_chain WHERE id = ${resolvedId} LIMIT 1
131+
`);
132+
const isMonitor = monitorQuery.numRows() > 0;
133+
134+
if (!isMonitor) {
135+
const tidMatch = typeRow.name.match(/\(owner tid: (\d+)\)/);
136+
const ownerTid = tidMatch ? parseInt(tidMatch[1], 10) : undefined;
137+
138+
const blockedQuery = await this.trace.engine.query(`
139+
SELECT
140+
t.tid as blocked_tid,
141+
t.name as blocked_name,
142+
t.is_main_thread,
143+
p.upid
144+
FROM slice s
145+
JOIN thread_track tt ON s.track_id = tt.id
146+
JOIN thread t USING (utid)
147+
LEFT JOIN process p USING (upid)
148+
WHERE s.id = ${resolvedId}
149+
LIMIT 1
150+
`);
151+
152+
let blockedThreadName = 'Unknown Thread';
153+
let blockedThreadTid: number | null = null;
154+
let isBlockedThreadMain = false;
155+
let upid: number | null = null;
156+
if (blockedQuery.numRows() > 0) {
157+
const r = blockedQuery.firstRow({
158+
blocked_tid: NUM,
159+
blocked_name: STR_NULL,
160+
is_main_thread: NUM_NULL,
161+
upid: NUM_NULL,
162+
});
163+
blockedThreadTid = r.blocked_tid;
164+
blockedThreadName = r.blocked_name || 'Unknown Thread';
165+
isBlockedThreadMain = r.is_main_thread === 1;
166+
upid = r.upid;
167+
}
168+
169+
let blockingThreadName = 'Unknown Thread';
170+
if (ownerTid !== undefined) {
171+
// First try same process
172+
if (upid !== null) {
173+
const blockingQuery = await this.trace.engine.query(`
174+
SELECT name FROM thread WHERE tid = ${ownerTid} AND upid = ${upid} LIMIT 1
175+
`);
176+
if (blockingQuery.numRows() > 0) {
177+
blockingThreadName =
178+
blockingQuery.firstRow({name: STR_NULL}).name || 'Unknown Thread';
179+
}
180+
}
181+
182+
// Fallback to any process if still unknown
183+
if (blockingThreadName === 'Unknown Thread') {
184+
const fallbackQuery = await this.trace.engine.query(`
185+
SELECT name FROM thread WHERE tid = ${ownerTid} LIMIT 1
186+
`);
187+
if (fallbackQuery.numRows() > 0) {
188+
blockingThreadName =
189+
fallbackQuery.firstRow({name: STR_NULL}).name || 'Unknown Thread';
190+
}
191+
}
192+
}
193+
194+
let blockingTrackUri: string | undefined = undefined;
195+
if (ownerTid !== undefined) {
196+
blockingTrackUri = 'com.android.AndroidLockContention#OwnerEvents';
197+
}
198+
199+
return {
200+
id: resolvedId,
201+
ts: Time.fromRaw(typeRow.ts),
202+
dur: typeRow.dur !== null ? Duration.fromRaw(typeRow.dur) : null,
203+
monotonicDur: null,
204+
waiterCount: 0,
205+
lockName: typeRow.name,
206+
blockedThreadName,
207+
blockedThreadTid,
208+
isBlockedThreadMain,
209+
blockedMethod: '',
210+
blockedSrc: '',
211+
blockingThreadName,
212+
blockingThreadTid: ownerTid ?? null,
213+
isBlockingThreadMain: false, // We don't easily know if owner is main thread without another query or join, but it's fine for now.
214+
blockingMethod: '',
215+
blockingSrc: '',
216+
parentId: null,
217+
blockingTrackUri,
218+
binderReplyId: null,
219+
blockingBinderTxnId: null,
220+
waiters: [],
221+
threadStates: [],
222+
blockedFunctions: [],
223+
};
224+
}
225+
97226
const mainQuery = await this.trace.engine.query(`
98227
SELECT
99228
id,
@@ -120,7 +249,7 @@ export class AndroidLockContentionEventSource {
120249
(SELECT id FROM thread_track WHERE utid = blocking_utid) as blocking_track_id,
121250
binder_reply_id
122251
FROM android_monitor_contention_chain
123-
WHERE id = ${eventId}
252+
WHERE id = ${resolvedId}
124253
`);
125254

126255
if (mainQuery.numRows() === 0) return null;

0 commit comments

Comments
 (0)