• 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';
16import {Anchor} from '../../widgets/anchor';
17import {Button} from '../../widgets/button';
18import {DetailsShell} from '../../widgets/details_shell';
19import {GridLayout} from '../../widgets/grid_layout';
20import {Section} from '../../widgets/section';
21import {SqlRef} from '../../widgets/sql_ref';
22import {Tree, TreeNode} from '../../widgets/tree';
23import {Intent} from '../../widgets/common';
24import {SchedSqlId} from '../../components/sql_utils/core_types';
25import {
26  getThreadState,
27  getThreadStateFromConstraints,
28  ThreadState,
29} from '../../components/sql_utils/thread_state';
30import {DurationWidget} from '../../components/widgets/duration';
31import {Timestamp} from '../../components/widgets/timestamp';
32import {getProcessName} from '../../components/sql_utils/process';
33import {
34  getFullThreadName,
35  getThreadName,
36} from '../../components/sql_utils/thread';
37import {ThreadStateRef} from '../../components/widgets/thread_state';
38import {
39  CRITICAL_PATH_LITE_CMD,
40} from '../../public/exposed_commands';
41import {goToSchedSlice} from '../../components/widgets/sched';
42import {TrackEventDetailsPanel} from '../../public/details_panel';
43import {Trace} from '../../public/trace';
44import {formatDuration} from '../../components/time_utils';
45
46interface RelatedThreadStates {
47  prev?: ThreadState;
48  next?: ThreadState;
49  waker?: ThreadState;
50  wakerInterruptCtx?: boolean;
51  wakee?: ThreadState[];
52}
53
54export class ThreadStateDetailsPanel implements TrackEventDetailsPanel {
55  private threadState?: ThreadState;
56  private relatedStates?: RelatedThreadStates;
57
58  constructor(
59    private readonly trace: Trace,
60    private readonly id: number,
61  ) {}
62
63  async load() {
64    const id = this.id;
65    this.threadState = await getThreadState(this.trace.engine, id);
66
67    if (!this.threadState) {
68      return;
69    }
70
71    const relatedStates: RelatedThreadStates = {};
72    relatedStates.prev = (
73      await getThreadStateFromConstraints(this.trace.engine, {
74        filters: [
75          `ts + dur = ${this.threadState.ts}`,
76          `utid = ${this.threadState.thread?.utid}`,
77        ],
78        limit: 1,
79      })
80    )[0];
81    relatedStates.next = (
82      await getThreadStateFromConstraints(this.trace.engine, {
83        filters: [
84          `ts = ${this.threadState.ts + this.threadState.dur}`,
85          `utid = ${this.threadState.thread?.utid}`,
86        ],
87        limit: 1,
88      })
89    )[0];
90
91    // note: this might be valid even if there is no |waker| slice, in the case
92    // of an interrupt wakeup while in the idle process (which is omitted from
93    // the thread_state table).
94    relatedStates.wakerInterruptCtx = this.threadState.wakerInterruptCtx;
95
96    if (this.threadState.wakerId !== undefined) {
97      relatedStates.waker = await getThreadState(
98        this.trace.engine,
99        this.threadState.wakerId,
100      );
101    } else if (
102      this.threadState.state == 'Running' &&
103      relatedStates.prev.wakerId != undefined
104    ) {
105      // For running slices, extract waker info from the preceding runnable.
106      relatedStates.waker = await getThreadState(
107        this.trace.engine,
108        relatedStates.prev.wakerId,
109      );
110      relatedStates.wakerInterruptCtx = relatedStates.prev.wakerInterruptCtx;
111    }
112
113    relatedStates.wakee = await getThreadStateFromConstraints(
114      this.trace.engine,
115      {
116        filters: [
117          `waker_id = ${id}`,
118          `(irq_context is null or irq_context = 0)`,
119        ],
120      },
121    );
122    this.relatedStates = relatedStates;
123  }
124
125  render() {
126    // TODO(altimin/stevegolton): Differentiate between "Current Selection" and
127    // "Pinned" views in DetailsShell.
128    return m(
129      DetailsShell,
130      {title: 'Thread State', description: this.renderLoadingText()},
131      m(
132        GridLayout,
133        m(
134          Section,
135          {title: 'Details'},
136          this.threadState && this.renderTree(this.threadState),
137        ),
138        m(
139          Section,
140          {title: 'Related thread states'},
141          this.renderRelatedThreadStates(),
142        ),
143      ),
144    );
145  }
146
147  private renderLoadingText() {
148    if (!this.threadState) {
149      return 'Loading';
150    }
151    return this.id;
152  }
153
154  private renderTree(threadState: ThreadState) {
155    const thread = threadState.thread;
156    const process = threadState.thread?.process;
157    return m(
158      Tree,
159      m(TreeNode, {
160        left: 'Start time',
161        right: m(Timestamp, {ts: threadState.ts}),
162      }),
163      m(TreeNode, {
164        left: 'Duration',
165        right: m(DurationWidget, {dur: threadState.dur}),
166      }),
167      m(TreeNode, {
168        left: 'State',
169        right: this.renderState(
170          threadState.state,
171          threadState.cpu,
172          threadState.schedSqlId,
173        ),
174      }),
175      threadState.blockedFunction &&
176        m(TreeNode, {
177          left: 'Blocked function',
178          right: threadState.blockedFunction,
179        }),
180      process &&
181        m(TreeNode, {
182          left: 'Process',
183          right: getProcessName(process),
184        }),
185      thread && m(TreeNode, {left: 'Thread', right: getThreadName(thread)}),
186      threadState.priority !== undefined &&
187        m(TreeNode, {
188          left: 'Priority',
189          right: threadState.priority,
190        }),
191      m(TreeNode, {
192        left: 'SQL ID',
193        right: m(SqlRef, {table: 'thread_state', id: threadState.id}),
194      }),
195    );
196  }
197
198  private renderState(
199    state: string,
200    cpu: number | undefined,
201    id: SchedSqlId | undefined,
202  ): m.Children {
203    if (!state) {
204      return null;
205    }
206    if (id === undefined || cpu === undefined) {
207      return state;
208    }
209    return m(
210      Anchor,
211      {
212        title: 'Go to CPU slice',
213        icon: 'call_made',
214        onclick: () => goToSchedSlice(id),
215      },
216      `${state} on CPU ${cpu}`,
217    );
218  }
219
220  private renderRelatedThreadStates(): m.Children {
221    if (this.threadState === undefined || this.relatedStates === undefined) {
222      return 'Loading';
223    }
224    const startTs = this.threadState.ts;
225    const renderRef = (state: ThreadState, name?: string) =>
226      m(ThreadStateRef, {
227        id: state.id,
228        name,
229      });
230
231    const nameForNextOrPrev = (threadState: ThreadState) =>
232      `${threadState.state} for ${formatDuration(this.trace, threadState.dur)}`;
233
234    const renderWaker = (related: RelatedThreadStates) => {
235      // Could be absent if:
236      // * this thread state wasn't woken up (e.g. it is a running slice).
237      // * the wakeup is from an interrupt during the idle process (which
238      //   isn't populated in thread_state).
239      // * at the start of the trace, before all per-cpu scheduling is known.
240      const hasWakerId = related.waker !== undefined;
241      // Interrupt context for the wakeups is absent from older traces.
242      const hasInterruptCtx = related.wakerInterruptCtx !== undefined;
243
244      if (!hasWakerId && !hasInterruptCtx) {
245        return null;
246      }
247      if (related.wakerInterruptCtx) {
248        return m(TreeNode, {
249          left: 'Woken by',
250          right: `Interrupt`,
251        });
252      }
253      return (
254        related.waker &&
255        m(TreeNode, {
256          left: hasInterruptCtx ? 'Woken by' : 'Woken by (maybe interrupt)',
257          right: renderRef(
258            related.waker,
259            getFullThreadName(related.waker.thread),
260          ),
261        })
262      );
263    };
264
265    const renderWakees = (related: RelatedThreadStates) => {
266      if (related.wakee === undefined || related.wakee.length == 0) {
267        return null;
268      }
269      const hasInterruptCtx = related.wakee[0].wakerInterruptCtx !== undefined;
270      return m(
271        TreeNode,
272        {
273          left: hasInterruptCtx
274            ? 'Woken threads'
275            : 'Woken threads (maybe interrupt)',
276        },
277        related.wakee.map((state) =>
278          m(TreeNode, {
279            left: m(Timestamp, {
280              ts: state.ts,
281              display: `+${formatDuration(this.trace, state.ts - startTs)}`,
282            }),
283            right: renderRef(state, getFullThreadName(state.thread)),
284          }),
285        ),
286      );
287    };
288
289    return [
290      m(
291        Tree,
292        this.relatedStates.prev &&
293          m(TreeNode, {
294            left: 'Previous state',
295            right: renderRef(
296              this.relatedStates.prev,
297              nameForNextOrPrev(this.relatedStates.prev),
298            ),
299          }),
300        this.relatedStates.next &&
301          m(TreeNode, {
302            left: 'Next state',
303            right: renderRef(
304              this.relatedStates.next,
305              nameForNextOrPrev(this.relatedStates.next),
306            ),
307          }),
308        renderWaker(this.relatedStates),
309        renderWakees(this.relatedStates),
310      ),
311      this.trace.commands.hasCommand(CRITICAL_PATH_LITE_CMD) &&
312        m(Button, {
313          label: 'Critical path lite',
314          intent: Intent.Primary,
315          onclick: () => {
316            this.trace.commands.runCommand(
317              CRITICAL_PATH_LITE_CMD,
318              this.threadState?.thread?.utid,
319            );
320          },
321        }),
322    ];
323  }
324
325  isLoading() {
326    return this.threadState === undefined || this.relatedStates === undefined;
327  }
328}
329