1// Copyright (C) 2023 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use size file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import m from 'mithril'; 16 17import {Icons} from '../base/semantic_icons'; 18import {duration, Time, time} from '../base/time'; 19import {exists} from '../base/utils'; 20import {Actions} from '../common/actions'; 21import {translateState} from '../common/thread_state'; 22import {Engine} from '../trace_processor/engine'; 23import {LONG, NUM, NUM_NULL, STR_NULL} from '../trace_processor/query_result'; 24import { 25 constraintsToQuerySuffix, 26 fromNumNull, 27 SQLConstraints, 28} from '../trace_processor/sql_utils'; 29import {Anchor} from '../widgets/anchor'; 30 31import {globals} from './globals'; 32import {scrollToTrackAndTs} from './scroll_helper'; 33import {asUtid, SchedSqlId, ThreadStateSqlId, Utid} from './sql_types'; 34import {getThreadInfo, ThreadInfo} from './thread_and_process_info'; 35import { 36 CPU_SLICE_TRACK_KIND, 37 THREAD_STATE_TRACK_KIND, 38} from '../core/track_kinds'; 39 40// Representation of a single thread state object, corresponding to 41// a row for the |thread_slice| table. 42export interface ThreadState { 43 // Id into |thread_state| table. 44 threadStateSqlId: ThreadStateSqlId; 45 // Id of the corresponding entry in the |sched| table. 46 schedSqlId?: SchedSqlId; 47 // Timestamp of the beginning of this thread state in nanoseconds. 48 ts: time; 49 // Duration of this thread state in nanoseconds. 50 dur: duration; 51 // CPU id if this thread state corresponds to a thread running on the CPU. 52 cpu?: number; 53 // Human-readable name of this thread state. 54 state: string; 55 blockedFunction?: string; 56 57 thread?: ThreadInfo; 58 wakerThread?: ThreadInfo; 59} 60 61// Gets a list of thread state objects from Trace Processor with given 62// constraints. 63export async function getThreadStateFromConstraints( 64 engine: Engine, 65 constraints: SQLConstraints, 66): Promise<ThreadState[]> { 67 const query = await engine.query(` 68 SELECT 69 thread_state.id as threadStateSqlId, 70 (select sched.id 71 from sched 72 where sched.ts=thread_state.ts and sched.utid=thread_state.utid 73 limit 1 74 ) as schedSqlId, 75 ts, 76 thread_state.dur as dur, 77 thread_state.cpu as cpu, 78 state, 79 thread_state.blocked_function as blockedFunction, 80 io_wait as ioWait, 81 thread_state.utid as utid, 82 waker_utid as wakerUtid 83 FROM thread_state 84 ${constraintsToQuerySuffix(constraints)}`); 85 const it = query.iter({ 86 threadStateSqlId: NUM, 87 schedSqlId: NUM_NULL, 88 ts: LONG, 89 dur: LONG, 90 cpu: NUM_NULL, 91 state: STR_NULL, 92 blockedFunction: STR_NULL, 93 ioWait: NUM_NULL, 94 utid: NUM, 95 wakerUtid: NUM_NULL, 96 }); 97 98 const result: ThreadState[] = []; 99 100 for (; it.valid(); it.next()) { 101 const ioWait = it.ioWait === null ? undefined : it.ioWait > 0; 102 // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions 103 const wakerUtid = asUtid(it.wakerUtid || undefined); 104 105 // TODO(altimin): Consider fetcing thread / process info using a single 106 // query instead of one per row. 107 result.push({ 108 threadStateSqlId: it.threadStateSqlId as ThreadStateSqlId, 109 schedSqlId: fromNumNull(it.schedSqlId) as SchedSqlId | undefined, 110 ts: Time.fromRaw(it.ts), 111 dur: it.dur, 112 cpu: fromNumNull(it.cpu), 113 state: translateState(it.state || undefined, ioWait), 114 blockedFunction: it.blockedFunction || undefined, 115 thread: await getThreadInfo(engine, asUtid(it.utid)), 116 wakerThread: wakerUtid 117 ? await getThreadInfo(engine, wakerUtid) 118 : undefined, 119 }); 120 } 121 return result; 122} 123 124export async function getThreadState( 125 engine: Engine, 126 id: number, 127): Promise<ThreadState | undefined> { 128 const result = await getThreadStateFromConstraints(engine, { 129 filters: [`id=${id}`], 130 }); 131 if (result.length > 1) { 132 throw new Error(`thread_state table has more than one row with id ${id}`); 133 } 134 if (result.length === 0) { 135 return undefined; 136 } 137 return result[0]; 138} 139 140export function goToSchedSlice(cpu: number, id: SchedSqlId, ts: time) { 141 let trackId: string | undefined; 142 for (const track of Object.values(globals.state.tracks)) { 143 if (exists(track?.uri)) { 144 const trackInfo = globals.trackManager.resolveTrackInfo(track.uri); 145 if (trackInfo?.kind === CPU_SLICE_TRACK_KIND) { 146 if (trackInfo?.cpu === cpu) { 147 trackId = track.key; 148 break; 149 } 150 } 151 } 152 } 153 if (trackId === undefined) { 154 return; 155 } 156 globals.setLegacySelection( 157 { 158 kind: 'SCHED_SLICE', 159 id, 160 trackKey: trackId, 161 }, 162 { 163 clearSearch: true, 164 pendingScrollId: undefined, 165 switchToCurrentSelectionTab: true, 166 }, 167 ); 168 169 scrollToTrackAndTs(trackId, ts); 170} 171 172interface ThreadStateRefAttrs { 173 id: ThreadStateSqlId; 174 ts: time; 175 dur: duration; 176 utid: Utid; 177 // If not present, a placeholder name will be used. 178 name?: string; 179} 180 181export class ThreadStateRef implements m.ClassComponent<ThreadStateRefAttrs> { 182 view(vnode: m.Vnode<ThreadStateRefAttrs>) { 183 return m( 184 Anchor, 185 { 186 icon: Icons.UpdateSelection, 187 onclick: () => { 188 let trackKey: string | number | undefined; 189 for (const track of Object.values(globals.state.tracks)) { 190 const trackDesc = globals.trackManager.resolveTrackInfo(track.uri); 191 if ( 192 trackDesc && 193 trackDesc.kind === THREAD_STATE_TRACK_KIND && 194 trackDesc.utid === vnode.attrs.utid 195 ) { 196 trackKey = track.key; 197 } 198 } 199 200 /* eslint-disable @typescript-eslint/strict-boolean-expressions */ 201 if (trackKey) { 202 /* eslint-enable */ 203 globals.makeSelection( 204 Actions.selectThreadState({ 205 id: vnode.attrs.id, 206 trackKey: trackKey.toString(), 207 }), 208 ); 209 210 scrollToTrackAndTs(trackKey, vnode.attrs.ts, true); 211 } 212 }, 213 }, 214 vnode.attrs.name ?? `Thread State ${vnode.attrs.id}`, 215 ); 216 } 217} 218 219export function threadStateRef(state: ThreadState): m.Child { 220 if (state.thread === undefined) return null; 221 222 return m(ThreadStateRef, { 223 id: state.threadStateSqlId, 224 ts: state.ts, 225 dur: state.dur, 226 utid: state.thread?.utid, 227 }); 228} 229