blob: 80f690b9b81652e7ae8c6326703404ef4bd116a4 [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 m from 'mithril';
import {Icons} from '../base/semantic_icons';
import {
duration,
Time,
time,
} from '../base/time';
import {exists} from '../base/utils';
import {Actions} from '../common/actions';
import {EngineProxy} from '../common/engine';
import {pluginManager} from '../common/plugins';
import {LONG, NUM, NUM_NULL, STR_NULL} from '../common/query_result';
import {translateState} from '../common/thread_state';
import {CPU_SLICE_TRACK_KIND} from '../tracks/cpu_slices';
import {Anchor} from '../widgets/anchor';
import {globals} from './globals';
import {scrollToTrackAndTs} from './scroll_helper';
import {
asUtid,
SchedSqlId,
ThreadStateSqlId,
Utid,
} from './sql_types';
import {
constraintsToQuerySuffix,
fromNumNull,
SQLConstraints,
} from './sql_utils';
import {
getThreadInfo,
ThreadInfo,
} from './thread_and_process_info';
// 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: 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;
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
${constraintsToQuerySuffix(constraints)}`);
const it = query.iter({
threadStateSqlId: NUM,
schedSqlId: NUM_NULL,
ts: LONG,
dur: LONG,
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: Time.fromRaw(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: time) {
let trackId: string|undefined;
for (const track of Object.values(globals.state.tracks)) {
if (exists(track?.uri)) {
const trackInfo = pluginManager.resolveTrackInfo(track.uri);
if (trackInfo?.kind === CPU_SLICE_TRACK_KIND) {
if (trackInfo?.cpu === cpu) {
trackId = track.id;
break;
}
}
}
}
if (trackId === undefined) {
return;
}
globals.makeSelection(Actions.selectSlice({id, trackId}));
scrollToTrackAndTs(trackId, ts);
}
interface ThreadStateRefAttrs {
id: ThreadStateSqlId;
ts: time;
dur: duration;
utid: Utid;
// If not present, a placeholder name will be used.
name?: string;
}
export class ThreadStateRef implements m.ClassComponent<ThreadStateRefAttrs> {
view(vnode: m.Vnode<ThreadStateRefAttrs>) {
return m(
Anchor,
{
icon: Icons.UpdateSelection,
onclick: () => {
let trackId: string|number|undefined;
for (const track of Object.values(globals.state.tracks)) {
if (track.kind === 'ThreadStateTrack' &&
(track.config as {utid: number}).utid === vnode.attrs.utid) {
trackId = track.id;
}
}
if (trackId) {
globals.makeSelection(Actions.selectThreadState({
id: vnode.attrs.id,
trackId: trackId.toString(),
}));
scrollToTrackAndTs(trackId, vnode.attrs.ts, true);
}
},
},
vnode.attrs.name ?? `Thread State ${vnode.attrs.id}`,
);
}
}
export function threadStateRef(state: ThreadState): m.Child {
if (state.thread === undefined) return null;
return m(ThreadStateRef, {
id: state.threadStateSqlId,
ts: state.ts,
dur: state.dur,
utid: state.thread?.utid,
});
}