diff --git a/ui/src/frontend/widgets/thread_state.ts b/ui/src/frontend/widgets/thread_state.ts index d6c0f7b19d..b7cfd692f9 100644 --- a/ui/src/frontend/widgets/thread_state.ts +++ b/ui/src/frontend/widgets/thread_state.ts @@ -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, }); } diff --git a/ui/src/plugins/dev.perfetto.ThreadState/table.ts b/ui/src/plugins/dev.perfetto.ThreadState/table.ts index 9c86c5cd3c..c93fb40fb5 100644 --- a/ui/src/plugins/dev.perfetto.ThreadState/table.ts +++ b/ui/src/plugins/dev.perfetto.ThreadState/table.ts @@ -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'), @@ -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, diff --git a/ui/src/plugins/dev.perfetto.ThreadState/thread_state_details_panel.ts b/ui/src/plugins/dev.perfetto.ThreadState/thread_state_details_panel.ts index b790f1df15..0a2287b764 100644 --- a/ui/src/plugins/dev.perfetto.ThreadState/thread_state_details_panel.ts +++ b/ui/src/plugins/dev.perfetto.ThreadState/thread_state_details_panel.ts @@ -52,7 +52,7 @@ interface RelatedThreadStates { } export class ThreadStateDetailsPanel implements TrackEventDetailsPanel { - private state?: ThreadState; + private threadState?: ThreadState; private relatedStates?: RelatedThreadStates; constructor( @@ -62,9 +62,9 @@ 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; } @@ -72,8 +72,8 @@ export class ThreadStateDetailsPanel implements TrackEventDetailsPanel { 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, }) @@ -81,22 +81,30 @@ export class ThreadStateDetailsPanel implements TrackEventDetailsPanel { 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, @@ -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, @@ -133,33 +141,37 @@ 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, { @@ -167,9 +179,14 @@ export class ThreadStateDetailsPanel implements TrackEventDetailsPanel { 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}), }), ); } @@ -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: @@ -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, ); }, }), @@ -305,7 +322,7 @@ export class ThreadStateDetailsPanel implements TrackEventDetailsPanel { onclick: () => { this.trace.commands.runCommand( CRITICAL_PATH_CMD, - this.state?.thread?.utid, + this.threadState?.thread?.utid, ); }, }), @@ -313,6 +330,6 @@ export class ThreadStateDetailsPanel implements TrackEventDetailsPanel { } isLoading() { - return this.state === undefined || this.relatedStates === undefined; + return this.threadState === undefined || this.relatedStates === undefined; } } diff --git a/ui/src/trace_processor/sql_utils/thread_state.ts b/ui/src/trace_processor/sql_utils/thread_state.ts index 92834fe57e..cc10a5f374 100644 --- a/ui/src/trace_processor/sql_utils/thread_state.ts +++ b/ui/src/trace_processor/sql_utils/thread_state.ts @@ -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. @@ -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 @@ -117,61 +119,63 @@ export async function getThreadStateFromConstraints( constraints: SQLConstraints, ): Promise { 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;