blob: 50ea7e5a26025fe4a3b2d865e7bea97bac1ad78b [file]
// Copyright (C) 2023 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use size 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 {type duration, Time, type time} from '../../base/time';
import type {Engine} from '../../trace_processor/engine';
import {
LONG,
NUM,
NUM_NULL,
STR_NULL,
} from '../../trace_processor/query_result';
import {
constraintsToQuerySuffix,
fromNumNull,
type SQLConstraints,
} from '../../trace_processor/sql_utils';
import {
asThreadStateSqlId,
asUtid,
type SchedSqlId,
type ThreadStateSqlId,
type Utid,
} from './core_types';
import {getThreadInfo, type ThreadInfo} from './thread';
const states: {[key: string]: string} = {
'R': 'Runnable',
'S': 'Sleeping',
'D': 'Uninterruptible Sleep',
'T': 'Stopped',
't': 'Traced',
'X': 'Exit (Dead)',
'Z': 'Exit (Zombie)',
'x': 'Task Dead',
'I': 'Idle',
'K': 'Wake Kill',
'W': 'Waking',
'P': 'Parked',
'N': 'No Load',
'+': '(Preempted)',
};
export function translateState(
state: string | undefined | null,
ioWait: boolean | undefined = undefined,
) {
if (state === undefined) return '';
// Self describing states
switch (state) {
case 'Created':
case 'Running':
case 'Initialized':
case 'DeferredReady':
case 'Transition':
case 'Standby':
case 'Waiting':
case 'Ready':
case 'Terminated':
return state;
}
if (state === null) {
return 'Unknown';
}
let result = states[state[0]];
if (ioWait === true) {
result += ' (IO)';
} else if (ioWait === false) {
result += ' (non-IO)';
}
for (let i = 1; i < state.length; i++) {
result += state[i] === '+' ? ' ' : ' + ';
result += states[state[i]];
}
// state is some string we don't know how to translate.
if (result === undefined) return state;
return result;
}
// Single thread state slice, corresponding to a row of |thread_slice| table.
export interface ThreadState {
// Id into |thread_state| table.
id: ThreadStateSqlId;
// Id of the corresponding entry in the |sched| table.
schedSqlId?: SchedSqlId;
// Timestamp of the beginning of this thread state in nanoseconds.
ts: time;
// Duration of this thread state in nanoseconds.
dur: duration;
// CPU id if this thread state corresponds to a thread running on the CPU.
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;
// 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;
// Kernel priority of this thread state.
priority?: number;
}
// Gets a list of thread state objects from Trace Processor with given
// constraints.
export async function getThreadStateFromConstraints(
engine: Engine,
constraints: SQLConstraints,
): Promise<ThreadState[]> {
const query = await engine.query(`
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({
id: NUM,
sched_id: NUM_NULL,
ts: LONG,
dur: LONG,
cpu: NUM_NULL,
state: STR_NULL,
blocked_function: STR_NULL,
io_wait: NUM_NULL,
utid: NUM,
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.io_wait === null ? undefined : it.io_wait > 0;
// TODO(altimin): Consider fetching thread / process info using a single
// query instead of one per row.
result.push({
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.blocked_function ?? undefined,
thread: await getThreadInfo(engine, asUtid(it.utid)),
wakerUtid: asUtid(it.waker_utid ?? undefined),
wakerId: asThreadStateSqlId(it.waker_id ?? undefined),
wakerInterruptCtx: fromNumNull(it.irq_context) as boolean | undefined,
priority: fromNumNull(it.priority),
});
}
return result;
}
export async function getThreadState(
engine: Engine,
id: number,
): Promise<ThreadState | undefined> {
const result = await getThreadStateFromConstraints(engine, {
filters: [`id=${id}`],
});
if (result.length > 1) {
throw new Error(`thread_state table has more than one row with id ${id}`);
}
if (result.length === 0) {
return undefined;
}
return result[0];
}