// 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 {translateState} from '../common/thread_state'; import {Engine} from '../trace_processor/engine'; import {LONG, NUM, NUM_NULL, STR_NULL} from '../trace_processor/query_result'; import { constraintsToQuerySuffix, fromNumNull, SQLConstraints, } from '../trace_processor/sql_utils'; import {Anchor} from '../widgets/anchor'; import {globals} from './globals'; import {scrollToTrackAndTs} from './scroll_helper'; import {asUtid, SchedSqlId, ThreadStateSqlId, Utid} from './sql_types'; import {getThreadInfo, ThreadInfo} from './thread_and_process_info'; import { CPU_SLICE_TRACK_KIND, THREAD_STATE_TRACK_KIND, } from '../core/track_kinds'; // 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: Engine, 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 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; // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions 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: Engine, id: number, ): Promise { 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 = globals.trackManager.resolveTrackInfo(track.uri); if (trackInfo?.kind === CPU_SLICE_TRACK_KIND) { if (trackInfo?.cpu === cpu) { trackId = track.key; break; } } } } if (trackId === undefined) { return; } globals.setLegacySelection( { kind: 'SCHED_SLICE', id, trackKey: trackId, }, { clearSearch: true, pendingScrollId: undefined, switchToCurrentSelectionTab: true, }, ); 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 { view(vnode: m.Vnode) { return m( Anchor, { icon: Icons.UpdateSelection, onclick: () => { let trackKey: string | number | undefined; for (const track of Object.values(globals.state.tracks)) { const trackDesc = globals.trackManager.resolveTrackInfo(track.uri); if ( trackDesc && trackDesc.kind === THREAD_STATE_TRACK_KIND && trackDesc.utid === vnode.attrs.utid ) { trackKey = track.key; } } /* eslint-disable @typescript-eslint/strict-boolean-expressions */ if (trackKey) { /* eslint-enable */ globals.makeSelection( Actions.selectThreadState({ id: vnode.attrs.id, trackKey: trackKey.toString(), }), ); scrollToTrackAndTs(trackKey, 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, }); }