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 {Actions} from '../common/actions'; 16import {EngineProxy} from '../common/engine'; 17import {LONG, NUM, NUM_NULL, STR_NULL} from '../common/query_result'; 18import {translateState} from '../common/thread_state'; 19import { 20 TPDuration, 21 TPTime, 22 tpTimeToCode, 23} from '../common/time'; 24 25import {copyToClipboard} from './clipboard'; 26import {globals} from './globals'; 27import {menuItem} from './popup_menu'; 28import {scrollToTrackAndTs} from './scroll_helper'; 29import { 30 asUtid, 31 SchedSqlId, 32 ThreadStateSqlId, 33} from './sql_types'; 34import { 35 constraintsToQueryFragment, 36 fromNumNull, 37 SQLConstraints, 38} from './sql_utils'; 39import { 40 getProcessName, 41 getThreadInfo, 42 getThreadName, 43 ThreadInfo, 44} from './thread_and_process_info'; 45import {dict, Dict, maybeValue, Value, value} from './value'; 46 47// Representation of a single thread state object, corresponding to 48// a row for the |thread_slice| table. 49export interface ThreadState { 50 // Id into |thread_state| table. 51 threadStateSqlId: ThreadStateSqlId; 52 // Id of the corresponding entry in the |sched| table. 53 schedSqlId?: SchedSqlId; 54 // Timestamp of the the beginning of this thread state in nanoseconds. 55 ts: TPTime; 56 // Duration of this thread state in nanoseconds. 57 dur: TPDuration; 58 // CPU id if this thread state corresponds to a thread running on the CPU. 59 cpu?: number; 60 // Human-readable name of this thread state. 61 state: string; 62 blockedFunction?: string; 63 64 thread?: ThreadInfo; 65 wakerThread?: ThreadInfo; 66} 67 68// Gets a list of thread state objects from Trace Processor with given 69// constraints. 70export async function getThreadStateFromConstraints( 71 engine: EngineProxy, constraints: SQLConstraints): Promise<ThreadState[]> { 72 const query = await engine.query(` 73 SELECT 74 thread_state.id as threadStateSqlId, 75 (select sched.id 76 from sched 77 where sched.ts=thread_state.ts and sched.utid=thread_state.utid 78 limit 1 79 ) as schedSqlId, 80 ts, 81 thread_state.dur as dur, 82 thread_state.cpu as cpu, 83 state, 84 thread_state.blocked_function as blockedFunction, 85 io_wait as ioWait, 86 thread_state.utid as utid, 87 waker_utid as wakerUtid 88 FROM thread_state 89 ${constraintsToQueryFragment(constraints)}`); 90 const it = query.iter({ 91 threadStateSqlId: NUM, 92 schedSqlId: NUM_NULL, 93 ts: LONG, 94 dur: LONG, 95 cpu: NUM_NULL, 96 state: STR_NULL, 97 blockedFunction: STR_NULL, 98 ioWait: NUM_NULL, 99 utid: NUM, 100 wakerUtid: NUM_NULL, 101 }); 102 103 const result: ThreadState[] = []; 104 105 for (; it.valid(); it.next()) { 106 const ioWait = it.ioWait === null ? undefined : it.ioWait > 0; 107 const wakerUtid = asUtid(it.wakerUtid || undefined); 108 109 // TODO(altimin): Consider fetcing thread / process info using a single 110 // query instead of one per row. 111 result.push({ 112 threadStateSqlId: it.threadStateSqlId as ThreadStateSqlId, 113 schedSqlId: fromNumNull(it.schedSqlId) as (SchedSqlId | undefined), 114 ts: it.ts, 115 dur: it.dur, 116 cpu: fromNumNull(it.cpu), 117 state: translateState(it.state || undefined, ioWait), 118 blockedFunction: it.blockedFunction || undefined, 119 thread: await getThreadInfo(engine, asUtid(it.utid)), 120 wakerThread: wakerUtid ? await getThreadInfo(engine, wakerUtid) : 121 undefined, 122 }); 123 } 124 return result; 125} 126 127export async function getThreadState( 128 engine: EngineProxy, id: number): Promise<ThreadState|undefined> { 129 const result = await getThreadStateFromConstraints(engine, { 130 filters: [`id=${id}`], 131 }); 132 if (result.length > 1) { 133 throw new Error(`thread_state table has more than one row with id ${id}`); 134 } 135 if (result.length === 0) { 136 return undefined; 137 } 138 return result[0]; 139} 140 141export function goToSchedSlice(cpu: number, id: SchedSqlId, ts: TPTime) { 142 let trackId: string|undefined; 143 for (const track of Object.values(globals.state.tracks)) { 144 if (track.kind === 'CpuSliceTrack' && 145 (track.config as {cpu: number}).cpu === cpu) { 146 trackId = track.id; 147 } 148 } 149 if (trackId === undefined) { 150 return; 151 } 152 globals.makeSelection(Actions.selectSlice({id, trackId})); 153 scrollToTrackAndTs(trackId, ts); 154} 155 156function stateToValue( 157 state: string, cpu: number|undefined, id: SchedSqlId|undefined, ts: TPTime): 158 Value|null { 159 if (!state) { 160 return null; 161 } 162 if (id === undefined || cpu === undefined) { 163 return value(state); 164 } 165 return value(`${state} on CPU ${cpu}`, { 166 rightButton: { 167 action: () => { 168 goToSchedSlice(cpu, id, ts); 169 }, 170 hoverText: 'Go to CPU slice', 171 }, 172 }); 173} 174 175export function threadStateToDict(state: ThreadState): Dict { 176 const result: {[name: string]: Value|null} = {}; 177 178 result['Start time'] = 179 value(tpTimeToCode(state.ts - globals.state.traceTime.start)); 180 result['Duration'] = value(tpTimeToCode(state.dur)); 181 result['State'] = 182 stateToValue(state.state, state.cpu, state.schedSqlId, state.ts); 183 result['Blocked function'] = maybeValue(state.blockedFunction); 184 const process = state?.thread?.process; 185 result['Process'] = maybeValue(process ? getProcessName(process) : undefined); 186 const thread = state?.thread; 187 result['Thread'] = maybeValue(thread ? getThreadName(thread) : undefined); 188 if (state.wakerThread) { 189 const process = state.wakerThread.process; 190 result['Waker'] = dict({ 191 'Process': maybeValue(process ? getProcessName(process) : undefined), 192 'Thread': maybeValue(getThreadName(state.wakerThread)), 193 }); 194 } 195 result['SQL id'] = value(`thread_state[${state.threadStateSqlId}]`, { 196 contextMenu: [ 197 menuItem( 198 'Copy SQL query', 199 () => { 200 copyToClipboard(`select * from thread_state where id=${ 201 state.threadStateSqlId}`); 202 }), 203 ], 204 }); 205 206 return dict(result); 207} 208