• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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