blob: c7031f229336cb0603a719c0c3357b0612218e1e [file] [log] [blame]
// 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 {Actions} from '../common/actions';
import {EngineProxy} from '../common/engine';
import {LONG, NUM, NUM_NULL, STR_NULL} from '../common/query_result';
import {translateState} from '../common/thread_state';
import {fromNs, timeToCode} from '../common/time';
import {copyToClipboard} from './clipboard';
import {globals} from './globals';
import {menuItem} from './popup_menu';
import {scrollToTrackAndTs} from './scroll_helper';
import {
asUtid,
SchedSqlId,
ThreadStateSqlId,
timestampFromNanos,
toTraceTime,
TPTimestamp,
} from './sql_types';
import {
constraintsToQueryFragment,
fromNumNull,
SQLConstraints,
} from './sql_utils';
import {
getProcessName,
getThreadInfo,
getThreadName,
ThreadInfo,
} from './thread_and_process_info';
import {dict, Dict, maybeValue, Value, value} from './value';
// Representation of a single thread state object, corresponding to
// a row for the |thread_slice| table.
export interface ThreadState {
// Id into |thread_state| table.
threadStateSqlId: ThreadStateSqlId;
// Id of the corresponding entry in the |sched| table.
schedSqlId?: SchedSqlId;
// Timestamp of the beginning of this thread state in nanoseconds.
ts: TPTimestamp;
// Duration of this thread state in nanoseconds.
dur: number;
// 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;
blockedFunction?: string;
thread?: ThreadInfo;
wakerThread?: ThreadInfo;
}
// Gets a list of thread state objects from Trace Processor with given
// constraints.
export async function getThreadStateFromConstraints(
engine: EngineProxy, 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
FROM thread_state
${constraintsToQueryFragment(constraints)}`);
const it = query.iter({
threadStateSqlId: NUM,
schedSqlId: NUM_NULL,
ts: LONG,
dur: NUM,
cpu: NUM_NULL,
state: STR_NULL,
blockedFunction: STR_NULL,
ioWait: NUM_NULL,
utid: NUM,
wakerUtid: 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.
result.push({
threadStateSqlId: it.threadStateSqlId as ThreadStateSqlId,
schedSqlId: fromNumNull(it.schedSqlId) as (SchedSqlId | undefined),
ts: timestampFromNanos(it.ts),
dur: it.dur,
cpu: fromNumNull(it.cpu),
state: translateState(it.state || undefined, ioWait),
blockedFunction: it.blockedFunction || undefined,
thread: await getThreadInfo(engine, asUtid(it.utid)),
wakerThread: wakerUtid ? await getThreadInfo(engine, wakerUtid) :
undefined,
});
}
return result;
}
export async function getThreadState(
engine: EngineProxy, 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];
}
export function goToSchedSlice(cpu: number, id: SchedSqlId, ts: TPTimestamp) {
let trackId: string|undefined;
for (const track of Object.values(globals.state.tracks)) {
if (track.kind === 'CpuSliceTrack' &&
(track.config as {cpu: number}).cpu === cpu) {
trackId = track.id;
}
}
if (trackId === undefined) {
return;
}
globals.makeSelection(Actions.selectSlice({id, trackId}));
// TODO(stevegolton): scrollToTrackAndTs() should take a TPTimestamp
scrollToTrackAndTs(trackId, Number(ts));
}
function stateToValue(
state: string,
cpu: number|undefined,
id: SchedSqlId|undefined,
ts: TPTimestamp): Value|null {
if (!state) {
return null;
}
if (id === undefined || cpu === undefined) {
return value(state);
}
return value(`${state} on CPU ${cpu}`, {
rightButton: {
action: () => {
goToSchedSlice(cpu, id, ts);
},
hoverText: 'Go to CPU slice',
},
});
}
export function threadStateToDict(state: ThreadState): Dict {
const result: {[name: string]: Value|null} = {};
result['Start time'] = value(timeToCode(toTraceTime(state.ts)));
result['Duration'] = value(timeToCode(fromNs(state.dur)));
result['State'] =
stateToValue(state.state, state.cpu, state.schedSqlId, state.ts);
result['Blocked function'] = maybeValue(state.blockedFunction);
const process = state?.thread?.process;
result['Process'] = maybeValue(process ? getProcessName(process) : undefined);
const thread = state?.thread;
result['Thread'] = maybeValue(thread ? getThreadName(thread) : undefined);
if (state.wakerThread) {
const process = state.wakerThread.process;
result['Waker'] = dict({
'Process': maybeValue(process ? getProcessName(process) : undefined),
'Thread': maybeValue(getThreadName(state.wakerThread)),
});
}
result['SQL id'] = value(`thread_state[${state.threadStateSqlId}]`, {
contextMenu: [
menuItem(
'Copy SQL query',
() => {
copyToClipboard(`select * from thread_state where id=${
state.threadStateSqlId}`);
}),
],
});
return dict(result);
}