| // 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]; | |
| } |