• 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 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