xref: /aosp_15_r20/external/perfetto/ui/src/plugins/dev.perfetto.CpuSlices/sched_details_tab.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1// Copyright (C) 2019 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 {DetailsShell} from '../../widgets/details_shell';
18import {GridLayout} from '../../widgets/grid_layout';
19import {Section} from '../../widgets/section';
20import {SqlRef} from '../../widgets/sql_ref';
21import {Tree, TreeNode} from '../../widgets/tree';
22import {DurationWidget} from '../../components/widgets/duration';
23import {Timestamp} from '../../components/widgets/timestamp';
24import {asSchedSqlId} from '../../components/sql_utils/core_types';
25import {
26  getSched,
27  getSchedWakeupInfo,
28  Sched,
29  SchedWakeupInfo,
30} from '../../components/sql_utils/sched';
31import {exists} from '../../base/utils';
32import {translateState} from '../../components/sql_utils/thread_state';
33import {Trace} from '../../public/trace';
34import {TrackEventDetailsPanel} from '../../public/details_panel';
35import {TrackEventSelection} from '../../public/selection';
36import {ThreadDesc, ThreadMap} from '../dev.perfetto.Thread/threads';
37import {assetSrc} from '../../base/assets';
38
39const MIN_NORMAL_SCHED_PRIORITY = 100;
40
41function getDisplayName(
42  name: string | undefined,
43  id: number | undefined,
44): string | undefined {
45  if (name === undefined) {
46    return id === undefined ? undefined : `${id}`;
47  } else {
48    return id === undefined ? name : `${name} ${id}`;
49  }
50}
51
52interface Data {
53  sched: Sched;
54  wakeup?: SchedWakeupInfo;
55}
56
57export class SchedSliceDetailsPanel implements TrackEventDetailsPanel {
58  private details?: Data;
59
60  constructor(
61    private readonly trace: Trace,
62    private readonly threads: ThreadMap,
63  ) {}
64
65  async load({eventId}: TrackEventSelection) {
66    const sched = await getSched(this.trace.engine, asSchedSqlId(eventId));
67    if (sched === undefined) {
68      return;
69    }
70    const wakeup = await getSchedWakeupInfo(this.trace.engine, sched);
71    this.details = {sched, wakeup};
72    this.trace.scheduleFullRedraw();
73  }
74
75  render() {
76    if (this.details === undefined) {
77      return m(DetailsShell, {title: 'Sched', description: 'Loading...'});
78    }
79    const threadInfo = this.threads.get(this.details.sched.thread.utid);
80
81    return m(
82      DetailsShell,
83      {
84        title: 'CPU Sched Slice',
85        description: this.renderTitle(this.details),
86      },
87      m(
88        GridLayout,
89        this.renderDetails(this.details, threadInfo),
90        this.renderSchedLatencyInfo(this.details),
91      ),
92    );
93  }
94
95  private renderTitle(data: Data) {
96    const threadInfo = this.threads.get(data.sched.thread.utid);
97    if (!threadInfo) {
98      return null;
99    }
100    return `${threadInfo.procName} [${threadInfo.pid}]`;
101  }
102
103  private renderSchedLatencyInfo(data: Data): m.Children {
104    if (
105      data.wakeup?.wakeupTs === undefined ||
106      data.wakeup?.wakerUtid === undefined
107    ) {
108      return null;
109    }
110    return m(
111      Section,
112      {title: 'Scheduling Latency'},
113      m(
114        '.slice-details-latency-panel',
115        m('img.slice-details-image', {
116          src: assetSrc('assets/scheduling_latency.png'),
117        }),
118        this.renderWakeupText(data),
119        this.renderDisplayLatencyText(data),
120      ),
121    );
122  }
123
124  private renderWakeupText(data: Data): m.Children {
125    if (
126      data.wakeup?.wakerUtid === undefined ||
127      data.wakeup?.wakeupTs === undefined ||
128      data.wakeup?.wakerCpu === undefined
129    ) {
130      return null;
131    }
132    const threadInfo = this.threads.get(data.wakeup.wakerUtid);
133    if (!threadInfo) {
134      return null;
135    }
136    return m(
137      '.slice-details-wakeup-text',
138      m(
139        '',
140        `Wakeup @ `,
141        m(Timestamp, {ts: data.wakeup?.wakeupTs}),
142        ` on CPU ${data.wakeup.wakerCpu} by`,
143      ),
144      m('', `P: ${threadInfo.procName} [${threadInfo.pid}]`),
145      m('', `T: ${threadInfo.threadName} [${threadInfo.tid}]`),
146    );
147  }
148
149  private renderDisplayLatencyText(data: Data): m.Children {
150    if (data.wakeup?.wakeupTs === undefined) {
151      return null;
152    }
153
154    const latency = data.sched.ts - data.wakeup?.wakeupTs;
155    return m(
156      '.slice-details-latency-text',
157      m('', `Scheduling latency: `, m(DurationWidget, {dur: latency})),
158      m(
159        '.text-detail',
160        `This is the interval from when the task became eligible to run
161        (e.g. because of notifying a wait queue it was suspended on) to
162        when it started running.`,
163      ),
164    );
165  }
166
167  private renderPriorityText(priority?: number) {
168    if (priority === undefined) {
169      return undefined;
170    }
171    return priority < MIN_NORMAL_SCHED_PRIORITY
172      ? `${priority} (real-time)`
173      : `${priority}`;
174  }
175
176  protected getProcessThreadDetails(data: Data) {
177    const process = data.sched.thread.process;
178    return new Map<string, string | undefined>([
179      ['Thread', getDisplayName(data.sched.thread.name, data.sched.thread.tid)],
180      ['Process', getDisplayName(process?.name, process?.pid)],
181      ['User ID', exists(process?.uid) ? String(process?.uid) : undefined],
182      ['Package name', process?.packageName],
183      [
184        'Version code',
185        process?.versionCode !== undefined
186          ? String(process?.versionCode)
187          : undefined,
188      ],
189    ]);
190  }
191
192  private renderDetails(data: Data, threadInfo?: ThreadDesc): m.Children {
193    if (!threadInfo) {
194      return null;
195    }
196
197    const extras: m.Children = [];
198
199    for (const [key, value] of this.getProcessThreadDetails(data)) {
200      if (value !== undefined) {
201        extras.push(m(TreeNode, {left: key, right: value}));
202      }
203    }
204
205    const treeNodes = [
206      m(TreeNode, {
207        left: 'Process',
208        right: `${threadInfo.procName} [${threadInfo.pid}]`,
209      }),
210      m(TreeNode, {
211        left: 'Thread',
212        right: m(
213          Anchor,
214          {
215            icon: 'call_made',
216            onclick: () => {
217              this.goToThread(data);
218            },
219          },
220          `${threadInfo.threadName} [${threadInfo.tid}]`,
221        ),
222      }),
223      m(TreeNode, {
224        left: 'Cmdline',
225        right: threadInfo.cmdline,
226      }),
227      m(TreeNode, {
228        left: 'Start time',
229        right: m(Timestamp, {ts: data.sched.ts}),
230      }),
231      m(TreeNode, {
232        left: 'Duration',
233        right: m(DurationWidget, {dur: data.sched.dur}),
234      }),
235      m(TreeNode, {
236        left: 'Priority',
237        right: this.renderPriorityText(data.sched.priority),
238      }),
239      m(TreeNode, {
240        left: 'End State',
241        right: translateState(data.sched.endState),
242      }),
243      m(TreeNode, {
244        left: 'SQL ID',
245        right: m(SqlRef, {table: 'sched', id: data.sched.id}),
246      }),
247      ...extras,
248    ];
249
250    return m(Section, {title: 'Details'}, m(Tree, treeNodes));
251  }
252
253  goToThread(data: Data) {
254    if (data.sched.threadStateId) {
255      this.trace.selection.selectSqlEvent(
256        'thread_state',
257        data.sched.threadStateId,
258        {scrollToSelection: true},
259      );
260    }
261  }
262
263  renderCanvas() {}
264}
265