Skip to content

Commit

Permalink
ui: Render additional thread state information
Browse files Browse the repository at this point in the history
Fixes:199431666
Change-Id: I0cd8e064dace11cfa0b4eccadc47120c27bc7f59
  • Loading branch information
aMayzner committed Nov 11, 2024
1 parent ea011a2 commit 3c499f3
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 72 deletions.
2 changes: 1 addition & 1 deletion ui/src/frontend/widgets/thread_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,6 @@ export function threadStateRef(state: ThreadState): m.Child {
if (state.thread === undefined) return null;

return m(ThreadStateRef, {
id: state.threadStateSqlId,
id: state.id,
});
}
12 changes: 6 additions & 6 deletions ui/src/plugins/dev.perfetto.ThreadState/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function getThreadStateTable(): SqlTableDescription {
return {
name: 'thread_state',
columns: [
new ThreadStateIdColumn('id', {notNull: true}),
new ThreadStateIdColumn('threadStateSqlId', {notNull: true}),
new TimestampColumn('ts'),
new DurationColumn('dur'),
new StandardColumn('state'),
Expand All @@ -46,11 +46,11 @@ export function getThreadStateTable(): SqlTableDescription {
},
{title: 'upid (process)', notNull: true},
),
new StandardColumn('io_wait', {aggregationType: 'nominal'}),
new StandardColumn('blocked_function'),
new ThreadColumn('waker_utid', {title: 'Waker thread'}),
new ThreadStateIdColumn('waker_id'),
new StandardColumn('irq_context', {aggregationType: 'nominal'}),
new StandardColumn('ioWait', {aggregationType: 'nominal'}),
new StandardColumn('blockedFunction'),
new ThreadColumn('wakerUtid', {title: 'Waker thread'}),
new ThreadStateIdColumn('wakerId'),
new StandardColumn('irqContext', {aggregationType: 'nominal'}),
new StandardColumn('ucpu', {
aggregationType: 'nominal',
startsHidden: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ interface RelatedThreadStates {
}

export class ThreadStateDetailsPanel implements TrackEventDetailsPanel {
private state?: ThreadState;
private threadState?: ThreadState;
private relatedStates?: RelatedThreadStates;

constructor(
Expand All @@ -62,41 +62,49 @@ export class ThreadStateDetailsPanel implements TrackEventDetailsPanel {

async load() {
const id = this.id;
this.state = await getThreadState(this.trace.engine, id);
this.threadState = await getThreadState(this.trace.engine, id);

if (!this.state) {
if (!this.threadState) {
return;
}

const relatedStates: RelatedThreadStates = {};
relatedStates.prev = (
await getThreadStateFromConstraints(this.trace.engine, {
filters: [
`ts + dur = ${this.state.ts}`,
`utid = ${this.state.thread?.utid}`,
`ts + dur = ${this.threadState.ts}`,
`utid = ${this.threadState.thread?.utid}`,
],
limit: 1,
})
)[0];
relatedStates.next = (
await getThreadStateFromConstraints(this.trace.engine, {
filters: [
`ts = ${this.state.ts + this.state.dur}`,
`utid = ${this.state.thread?.utid}`,
`ts = ${this.threadState.ts + this.threadState.dur}`,
`utid = ${this.threadState.thread?.utid}`,
],
limit: 1,
})
)[0];
if (this.state.wakerId !== undefined) {
if (this.threadState.wakerId !== undefined) {
relatedStates.waker = await getThreadState(
this.trace.engine,
this.state.wakerId,
this.threadState.wakerId,
);
} else if (
this.threadState.state == 'Running' &&
relatedStates.prev.wakerId != undefined
) {
relatedStates.waker = await getThreadState(
this.trace.engine,
relatedStates.prev.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.wakerInterruptCtx = this.threadState.wakerInterruptCtx;

relatedStates.wakee = await getThreadStateFromConstraints(
this.trace.engine,
Expand All @@ -121,7 +129,7 @@ export class ThreadStateDetailsPanel implements TrackEventDetailsPanel {
m(
Section,
{title: 'Details'},
this.state && this.renderTree(this.state),
this.threadState && this.renderTree(this.threadState),
),
m(
Section,
Expand All @@ -133,43 +141,52 @@ export class ThreadStateDetailsPanel implements TrackEventDetailsPanel {
}

private renderLoadingText() {
if (!this.state) {
if (!this.threadState) {
return 'Loading';
}
return this.id;
}

private renderTree(state: ThreadState) {
const thread = state.thread;
const process = state.thread?.process;
private renderTree(threadState: ThreadState) {
const thread = threadState.thread;
const process = threadState.thread?.process;
return m(
Tree,
m(TreeNode, {
left: 'Start time',
right: m(Timestamp, {ts: state.ts}),
right: m(Timestamp, {ts: threadState.ts}),
}),
m(TreeNode, {
left: 'Duration',
right: m(DurationWidget, {dur: state.dur}),
right: m(DurationWidget, {dur: threadState.dur}),
}),
m(TreeNode, {
left: 'State',
right: this.renderState(state.state, state.cpu, state.schedSqlId),
right: this.renderState(
threadState.state,
threadState.cpu,
threadState.schedSqlId,
),
}),
state.blockedFunction &&
threadState.blockedFunction &&
m(TreeNode, {
left: 'Blocked function',
right: state.blockedFunction,
right: threadState.blockedFunction,
}),
process &&
m(TreeNode, {
left: 'Process',
right: getProcessName(process),
}),
thread && m(TreeNode, {left: 'Thread', right: getThreadName(thread)}),
threadState.priority !== undefined &&
m(TreeNode, {
left: 'Priority',
right: threadState.priority,
}),
m(TreeNode, {
left: 'SQL ID',
right: m(SqlRef, {table: 'thread_state', id: state.threadStateSqlId}),
right: m(SqlRef, {table: 'thread_state', id: threadState.id}),
}),
);
}
Expand Down Expand Up @@ -197,18 +214,18 @@ export class ThreadStateDetailsPanel implements TrackEventDetailsPanel {
}

private renderRelatedThreadStates(): m.Children {
if (this.state === undefined || this.relatedStates === undefined) {
if (this.threadState === undefined || this.relatedStates === undefined) {
return 'Loading';
}
const startTs = this.state.ts;
const startTs = this.threadState.ts;
const renderRef = (state: ThreadState, name?: string) =>
m(ThreadStateRef, {
id: state.threadStateSqlId,
id: state.id,
name,
});

const nameForNextOrPrev = (state: ThreadState) =>
`${state.state} for ${renderDuration(state.dur)}`;
const nameForNextOrPrev = (threadState: ThreadState) =>
`${threadState.state} for ${renderDuration(threadState.dur)}`;

const renderWaker = (related: RelatedThreadStates) => {
// Could be absent if:
Expand Down Expand Up @@ -294,7 +311,7 @@ export class ThreadStateDetailsPanel implements TrackEventDetailsPanel {
onclick: () => {
this.trace.commands.runCommand(
CRITICAL_PATH_LITE_CMD,
this.state?.thread?.utid,
this.threadState?.thread?.utid,
);
},
}),
Expand All @@ -305,14 +322,14 @@ export class ThreadStateDetailsPanel implements TrackEventDetailsPanel {
onclick: () => {
this.trace.commands.runCommand(
CRITICAL_PATH_CMD,
this.state?.thread?.utid,
this.threadState?.thread?.utid,
);
},
}),
];
}

isLoading() {
return this.state === undefined || this.relatedStates === undefined;
return this.threadState === undefined || this.relatedStates === undefined;
}
}
76 changes: 40 additions & 36 deletions ui/src/trace_processor/sql_utils/thread_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export function translateState(
// Single thread state slice, corresponding to a row of |thread_slice| table.
export interface ThreadState {
// Id into |thread_state| table.
threadStateSqlId: ThreadStateSqlId;
id: ThreadStateSqlId;
// Id of the corresponding entry in the |sched| table.
schedSqlId?: SchedSqlId;
// Timestamp of the beginning of this thread state in nanoseconds.
Expand All @@ -108,6 +108,8 @@ export interface ThreadState {
// unset even for runnable states, if the trace was recorded without
// interrupt information.
wakerInterruptCtx?: boolean;
// Kernel priority of this thread state.
priority?: number;
}

// Gets a list of thread state objects from Trace Processor with given
Expand All @@ -117,61 +119,63 @@ export async function getThreadStateFromConstraints(
constraints: SQLConstraints,
): Promise<ThreadState[]> {
const query = await engine.query(`
SELECT
thread_state.id as threadStateSqlId,
(select sched.id
from sched
where sched.ts=thread_state.ts and sched.utid=thread_state.utid
limit 1
) as schedSqlId,
ts,
thread_state.dur as dur,
thread_state.cpu as cpu,
state,
thread_state.blocked_function as blockedFunction,
io_wait as ioWait,
thread_state.utid as utid,
waker_utid as wakerUtid,
waker_id as wakerId,
irq_context as wakerInterruptCtx
FROM thread_state
WITH raw AS (
SELECT
ts.id,
sched.id AS sched_id,
ts.ts,
ts.dur,
ts.cpu,
ts.state,
ts.blocked_function,
ts.io_wait,
ts.utid,
ts.waker_utid,
ts.waker_id,
ts.irq_context,
sched.priority
FROM thread_state ts
LEFT JOIN sched USING (utid, ts)
)
SELECT * FROM raw
${constraintsToQuerySuffix(constraints)}`);
const it = query.iter({
threadStateSqlId: NUM,
schedSqlId: NUM_NULL,
id: NUM,
sched_id: NUM_NULL,
ts: LONG,
dur: LONG,
cpu: NUM_NULL,
state: STR_NULL,
blockedFunction: STR_NULL,
ioWait: NUM_NULL,
blocked_function: STR_NULL,
io_wait: NUM_NULL,
utid: NUM,
wakerUtid: NUM_NULL,
wakerId: NUM_NULL,
wakerInterruptCtx: NUM_NULL,
waker_utid: NUM_NULL,
waker_id: NUM_NULL,
irq_context: NUM_NULL,
priority: NUM_NULL,
});

const result: ThreadState[] = [];

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

// TODO(altimin): Consider fetcing thread / process info using a single
// TODO(altimin): Consider fetching thread / process info using a single
// query instead of one per row.
result.push({
threadStateSqlId: it.threadStateSqlId as ThreadStateSqlId,
schedSqlId: fromNumNull(it.schedSqlId) as SchedSqlId | undefined,
id: it.id as ThreadStateSqlId,
schedSqlId: fromNumNull(it.sched_id) as SchedSqlId | undefined,
ts: Time.fromRaw(it.ts),
dur: it.dur,
cpu: fromNumNull(it.cpu),
state: translateState(it.state ?? undefined, ioWait),
blockedFunction: it.blockedFunction ?? undefined,
blockedFunction: it.blocked_function ?? undefined,
thread: await getThreadInfo(engine, asUtid(it.utid)),
wakerUtid: asUtid(it.wakerUtid ?? undefined),
wakerId: asThreadStateSqlId(it.wakerId ?? undefined),
wakerInterruptCtx: fromNumNull(it.wakerInterruptCtx) as
| boolean
| undefined,
wakerUtid: asUtid(it.waker_id ?? undefined),
wakerId: asThreadStateSqlId(it.waker_id ?? undefined),
wakerInterruptCtx: fromNumNull(it.irq_context) as boolean | undefined,
priority: fromNumNull(it.priority),
});
}
return result;
Expand Down

0 comments on commit 3c499f3

Please sign in to comment.