Skip to content

Commit

Permalink
Merge "ui: use the interrupt context when showing scheduling wakeups"…
Browse files Browse the repository at this point in the history
… into main
  • Loading branch information
Treehugger Robot authored and Gerrit Code Review committed Sep 30, 2024
2 parents 9eecd90 + b124a04 commit ba40a4d
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 81 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ Unreleased:
Trace Processor:
*
UI:
*
* Scheduling wakeup information now reflects whether the wakeup came
from an interrupt context. The per-cpu scheduling tracks now show only
non-interrupt wakeups, while the per-thread thread state tracks either
link to an exact waker slice or state that the wakeup is from an
interrupt. Older traces that are recorded without interrupt context
information treat all wakeups as non-interrupt.
SDK:
*

Expand Down
9 changes: 5 additions & 4 deletions ui/src/core_plugins/cpu_slices/sched_details_tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export class SchedDetailsTab extends BottomTab<SchedDetailsTabConfig> {
private renderSchedLatencyInfo(data: Data): m.Children {
if (
data.wakeup?.wakeupTs === undefined ||
data.wakeup?.wakerThread === undefined
data.wakeup?.wakerUtid === undefined
) {
return null;
}
Expand All @@ -142,12 +142,13 @@ export class SchedDetailsTab extends BottomTab<SchedDetailsTabConfig> {

private renderWakeupText(data: Data): m.Children {
if (
data.wakeup?.wakerThread === undefined ||
data.wakeup?.wakeupTs === undefined
data.wakeup?.wakerUtid === undefined ||
data.wakeup?.wakeupTs === undefined ||
data.wakeup?.wakerCpu === undefined
) {
return null;
}
const threadInfo = globals.threads.get(data.wakeup.wakerThread.utid);
const threadInfo = globals.threads.get(data.wakeup.wakerUtid);
if (!threadInfo) {
return null;
}
Expand Down
128 changes: 71 additions & 57 deletions ui/src/frontend/thread_state_tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// limitations under the License.

import m from 'mithril';
import {Time, time} from '../base/time';
import {time} from '../base/time';
import {raf} from '../core/raf_scheduler';
import {Anchor} from '../widgets/anchor';
import {Button} from '../widgets/button';
Expand All @@ -38,7 +38,6 @@ import {Timestamp} from './widgets/timestamp';
import {globals} from './globals';
import {getProcessName} from '../trace_processor/sql_utils/process';
import {
ThreadInfo,
getFullThreadName,
getThreadName,
} from '../trace_processor/sql_utils/thread';
Expand All @@ -58,6 +57,7 @@ interface RelatedThreadStates {
prev?: ThreadState;
next?: ThreadState;
waker?: ThreadState;
wakerInterruptCtx?: boolean;
wakee?: ThreadState[];
}

Expand Down Expand Up @@ -107,23 +107,21 @@ export class ThreadStateTab extends BottomTab<ThreadStateTabConfig> {
limit: 1,
})
)[0];
if (this.state.wakerThread?.utid !== undefined) {
relatedStates.waker = (
await getThreadStateFromConstraints(this.engine, {
filters: [
`utid = ${this.state.wakerThread?.utid}`,
`ts <= ${this.state.ts}`,
`ts + dur >= ${this.state.ts}`,
],
})
)[0];
if (this.state.wakerId !== undefined) {
relatedStates.waker = await getThreadState(
this.engine,
this.state.wakerId,
);
}
// note: this might be valid even if there is no |waker| slice, in the case
// of an interrupt wakeup while in the idle process (which is omitted from
// the thread_state table).
relatedStates.wakerInterruptCtx = this.state.wakerInterruptCtx;

relatedStates.wakee = await getThreadStateFromConstraints(this.engine, {
filters: [
`waker_utid = ${this.state.thread?.utid}`,
`state = 'R'`,
`ts >= ${this.state.ts}`,
`ts <= ${this.state.ts + this.state.dur}`,
`waker_id = ${this.config.id}`,
`(irq_context is null or irq_context = 0)`,
],
});

Expand Down Expand Up @@ -201,7 +199,6 @@ export class ThreadStateTab extends BottomTab<ThreadStateTabConfig> {
right: getProcessName(process),
}),
thread && m(TreeNode, {left: 'Thread', right: getThreadName(thread)}),
state.wakerThread && this.renderWakerThread(state.wakerThread),
m(TreeNode, {
left: 'SQL ID',
right: m(SqlRef, {table: 'thread_state', id: state.threadStateSqlId}),
Expand Down Expand Up @@ -232,18 +229,6 @@ export class ThreadStateTab extends BottomTab<ThreadStateTabConfig> {
);
}

private renderWakerThread(wakerThread: ThreadInfo) {
return m(
TreeNode,
{left: 'Waker'},
m(TreeNode, {
left: 'Process',
right: getProcessName(wakerThread.process),
}),
m(TreeNode, {left: 'Thread', right: getThreadName(wakerThread)}),
);
}

private renderRelatedThreadStates(): m.Children {
if (this.state === undefined || this.relatedStates === undefined) {
return 'Loading';
Expand All @@ -261,17 +246,64 @@ export class ThreadStateTab extends BottomTab<ThreadStateTabConfig> {
const nameForNextOrPrev = (state: ThreadState) =>
`${state.state} for ${renderDuration(state.dur)}`;

const renderWaker = (related: RelatedThreadStates) => {
// Could be absent if:
// * this thread state wasn't woken up (e.g. it is a running slice).
// * the wakeup is from an interrupt during the idle process (which
// isn't populated in thread_state).
// * at the start of the trace, before all per-cpu scheduling is known.
const hasWakerId = related.waker !== undefined;
// Interrupt context for the wakeups is absent from older traces.
const hasInterruptCtx = related.wakerInterruptCtx !== undefined;

if (!hasWakerId && !hasInterruptCtx) {
return null;
}
if (related.wakerInterruptCtx) {
return m(TreeNode, {
left: 'Woken by',
right: `Interrupt`,
});
}
return (
related.waker &&
m(TreeNode, {
left: hasInterruptCtx ? 'Woken by' : 'Woken by (maybe interrupt)',
right: renderRef(
related.waker,
getFullThreadName(related.waker.thread),
),
})
);
};

const renderWakees = (related: RelatedThreadStates) => {
if (related.wakee === undefined || related.wakee.length == 0) {
return null;
}
const hasInterruptCtx = related.wakee[0].wakerInterruptCtx !== undefined;
return m(
TreeNode,
{
left: hasInterruptCtx
? 'Woken threads'
: 'Woken threads (maybe interrupt)',
},
related.wakee.map((state) =>
m(TreeNode, {
left: m(Timestamp, {
ts: state.ts,
display: `+${renderDuration(state.ts - startTs)}`,
}),
right: renderRef(state, getFullThreadName(state.thread)),
}),
),
);
};

return [
m(
Tree,
this.relatedStates.waker &&
m(TreeNode, {
left: 'Waker',
right: renderRef(
this.relatedStates.waker,
getFullThreadName(this.relatedStates.waker.thread),
),
}),
this.relatedStates.prev &&
m(TreeNode, {
left: 'Previous state',
Expand All @@ -288,26 +320,8 @@ export class ThreadStateTab extends BottomTab<ThreadStateTabConfig> {
nameForNextOrPrev(this.relatedStates.next),
),
}),
this.relatedStates.wakee &&
this.relatedStates.wakee.length > 0 &&
m(
TreeNode,
{
left: 'Woken threads',
},
this.relatedStates.wakee.map((state) =>
m(TreeNode, {
left: m(Timestamp, {
ts: state.ts,
display: [
'Start+',
m(DurationWidget, {dur: Time.sub(state.ts, startTs)}),
],
}),
right: renderRef(state, getFullThreadName(state.thread)),
}),
),
),
renderWaker(this.relatedStates),
renderWakees(this.relatedStates),
),
globals.commandManager.hasCommand(CRITICAL_PATH_LITE_CMD) &&
m(Button, {
Expand Down
23 changes: 13 additions & 10 deletions ui/src/trace_processor/sql_utils/sched.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
asUtid,
SchedSqlId,
ThreadStateSqlId,
Utid,
} from './core_types';
import {getThreadInfo, ThreadInfo} from './thread';
import {getThreadState, getThreadStateFromConstraints} from './thread_state';
Expand All @@ -46,7 +47,7 @@ export interface Sched {

export interface SchedWakeupInfo {
wakeupTs?: time;
wakerThread?: ThreadInfo;
wakerUtid?: Utid;
wakerCpu?: number;
}

Expand Down Expand Up @@ -116,30 +117,32 @@ export async function getSched(
return result[0];
}

// Returns the thread and time of the wakeup that resulted in this running
// sched slice. Omits wakeups that are known to be from interrupt context,
// since we cannot always recover the correct waker cpu with the current
// table layout.
export async function getSchedWakeupInfo(
engine: Engine,
sched: Sched,
): Promise<SchedWakeupInfo | undefined> {
const running = await getThreadStateFromConstraints(engine, {
const prevRunnable = await getThreadStateFromConstraints(engine, {
filters: [
'state="R"',
'state = "R"',
`ts + dur = ${sched.ts}`,
`utid = ${sched.thread.utid}`,
`(irq_context is null or irq_context = 0)`,
],
});
if (running.length === 0) {
if (prevRunnable.length === 0 || prevRunnable[0].wakerId === undefined) {
return undefined;
}
if (running[0].wakerId === undefined) {
return undefined;
}
const waker = await getThreadState(engine, running[0].wakerId);
const waker = await getThreadState(engine, prevRunnable[0].wakerId);
if (waker === undefined) {
return undefined;
}
return {
wakerCpu: waker?.cpu,
wakerThread: running[0].wakerThread,
wakeupTs: running[0].ts,
wakerUtid: prevRunnable[0].wakerUtid,
wakeupTs: prevRunnable[0].ts,
};
}
27 changes: 18 additions & 9 deletions ui/src/trace_processor/sql_utils/thread_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
asUtid,
SchedSqlId,
ThreadStateSqlId,
Utid,
} from './core_types';
import {getThreadInfo, ThreadInfo} from './thread';

Expand Down Expand Up @@ -81,8 +82,7 @@ export function translateState(
return result;
}

// Representation of a single thread state object, corresponding to
// a row for the |thread_slice| table.
// Single thread state slice, corresponding to a row of |thread_slice| table.
export interface ThreadState {
// Id into |thread_state| table.
threadStateSqlId: ThreadStateSqlId;
Expand All @@ -96,11 +96,18 @@ export interface ThreadState {
cpu?: number;
// Human-readable name of this thread state.
state: string;
// Kernel function where the thread has suspended.
blockedFunction?: string;

// Description of the thread itself.
thread?: ThreadInfo;
wakerThread?: ThreadInfo;
// Thread that was running when this thread was woken up.
wakerUtid?: Utid;
// Active thread state at the time of the wakeup.
wakerId?: ThreadStateSqlId;
// Was the wakeup from an interrupt context? It is possible for this to be
// unset even for runnable states, if the trace was recorded without
// interrupt information.
wakerInterruptCtx?: boolean;
}

// Gets a list of thread state objects from Trace Processor with given
Expand All @@ -125,7 +132,8 @@ export async function getThreadStateFromConstraints(
io_wait as ioWait,
thread_state.utid as utid,
waker_utid as wakerUtid,
waker_id as wakerId
waker_id as wakerId,
irq_context as wakerInterruptCtx
FROM thread_state
${constraintsToQuerySuffix(constraints)}`);
const it = query.iter({
Expand All @@ -140,13 +148,13 @@ export async function getThreadStateFromConstraints(
utid: NUM,
wakerUtid: NUM_NULL,
wakerId: NUM_NULL,
wakerInterruptCtx: NUM_NULL,
});

const result: ThreadState[] = [];

for (; it.valid(); it.next()) {
const ioWait = it.ioWait === null ? undefined : it.ioWait > 0;
const wakerUtid = asUtid(it.wakerUtid ?? undefined);

// TODO(altimin): Consider fetcing thread / process info using a single
// query instead of one per row.
Expand All @@ -159,10 +167,11 @@ export async function getThreadStateFromConstraints(
state: translateState(it.state ?? undefined, ioWait),
blockedFunction: it.blockedFunction ?? undefined,
thread: await getThreadInfo(engine, asUtid(it.utid)),
wakerThread: wakerUtid
? await getThreadInfo(engine, wakerUtid)
: undefined,
wakerUtid: asUtid(it.wakerUtid ?? undefined),
wakerId: asThreadStateSqlId(it.wakerId ?? undefined),
wakerInterruptCtx: fromNumNull(it.wakerInterruptCtx) as
| boolean
| undefined,
});
}
return result;
Expand Down

0 comments on commit ba40a4d

Please sign in to comment.