• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  }
73
74  render() {
75    if (this.details === undefined) {
76      return m(DetailsShell, {title: 'Sched', description: 'Loading...'});
77    }
78    const threadInfo = this.threads.get(this.details.sched.thread.utid);
79
80    return m(
81      DetailsShell,
82      {
83        title: 'CPU Sched Slice',
84        description: this.renderTitle(this.details),
85      },
86      m(
87        GridLayout,
88        this.renderDetails(this.details, threadInfo),
89        this.renderSchedLatencyInfo(this.details),
90      ),
91    );
92  }
93
94  private renderTitle(data: Data) {
95    const threadInfo = this.threads.get(data.sched.thread.utid);
96    if (!threadInfo) {
97      return null;
98    }
99    return `${threadInfo.procName} [${threadInfo.pid}]`;
100  }
101
102  private renderSchedLatencyInfo(data: Data): m.Children {
103    if (
104      data.wakeup?.wakeupTs === undefined ||
105      data.wakeup?.wakerUtid === undefined
106    ) {
107      return null;
108    }
109    return m(
110      Section,
111      {title: 'Scheduling Latency'},
112      m(
113        '.slice-details-latency-panel',
114        m('img.slice-details-image', {
115          src: assetSrc('assets/scheduling_latency.png'),
116        }),
117        this.renderWakeupText(data),
118        this.renderDisplayLatencyText(data),
119      ),
120    );
121  }
122
123  private renderWakeupText(data: Data): m.Children {
124    if (
125      data.wakeup?.wakerUtid === undefined ||
126      data.wakeup?.wakeupTs === undefined ||
127      data.wakeup?.wakerCpu === undefined
128    ) {
129      return null;
130    }
131    const threadInfo = this.threads.get(data.wakeup.wakerUtid);
132    if (!threadInfo) {
133      return null;
134    }
135    return m(
136      '.slice-details-wakeup-text',
137      m(
138        '',
139        `Wakeup @ `,
140        m(Timestamp, {ts: data.wakeup?.wakeupTs}),
141        ` on CPU ${data.wakeup.wakerCpu} by`,
142      ),
143      m('', `P: ${threadInfo.procName} [${threadInfo.pid}]`),
144      m('', `T: ${threadInfo.threadName} [${threadInfo.tid}]`),
145    );
146  }
147
148  private renderDisplayLatencyText(data: Data): m.Children {
149    if (data.wakeup?.wakeupTs === undefined) {
150      return null;
151    }
152
153    const latency = data.sched.ts - data.wakeup?.wakeupTs;
154    return m(
155      '.slice-details-latency-text',
156      m('', `Scheduling latency: `, m(DurationWidget, {dur: latency})),
157      m(
158        '.text-detail',
159        `This is the interval from when the task became eligible to run
160        (e.g. because of notifying a wait queue it was suspended on) to
161        when it started running.`,
162      ),
163    );
164  }
165
166  private renderPriorityText(priority?: number) {
167    if (priority === undefined) {
168      return undefined;
169    }
170    return priority < MIN_NORMAL_SCHED_PRIORITY
171      ? `${priority} (real-time)`
172      : `${priority}`;
173  }
174
175  protected getProcessThreadDetails(data: Data) {
176    const process = data.sched.thread.process;
177    return new Map<string, string | undefined>([
178      ['Thread', getDisplayName(data.sched.thread.name, data.sched.thread.tid)],
179      ['Process', getDisplayName(process?.name, process?.pid)],
180      ['User ID', exists(process?.uid) ? String(process?.uid) : undefined],
181      ['Package name', process?.packageName],
182      [
183        'Version code',
184        process?.versionCode !== undefined
185          ? String(process?.versionCode)
186          : undefined,
187      ],
188    ]);
189  }
190
191  private renderDetails(data: Data, threadInfo?: ThreadDesc): m.Children {
192    if (!threadInfo) {
193      return null;
194    }
195
196    const extras: m.Children = [];
197
198    for (const [key, value] of this.getProcessThreadDetails(data)) {
199      if (value !== undefined) {
200        extras.push(m(TreeNode, {left: key, right: value}));
201      }
202    }
203
204    const treeNodes = [
205      m(TreeNode, {
206        left: 'Process',
207        right: `${threadInfo.procName} [${threadInfo.pid}]`,
208      }),
209      m(TreeNode, {
210        left: 'Thread',
211        right: m(
212          Anchor,
213          {
214            icon: 'call_made',
215            onclick: () => {
216              this.goToThread(data);
217            },
218          },
219          `${threadInfo.threadName} [${threadInfo.tid}]`,
220        ),
221      }),
222      m(TreeNode, {
223        left: 'Cmdline',
224        right: threadInfo.cmdline,
225      }),
226      m(TreeNode, {
227        left: 'Start time',
228        right: m(Timestamp, {ts: data.sched.ts}),
229      }),
230      m(TreeNode, {
231        left: 'Duration',
232        right: m(DurationWidget, {dur: data.sched.dur}),
233      }),
234      m(TreeNode, {
235        left: 'Priority',
236        right: this.renderPriorityText(data.sched.priority),
237      }),
238      m(TreeNode, {
239        left: 'End State',
240        right: translateState(data.sched.endState),
241      }),
242      m(TreeNode, {
243        left: 'SQL ID',
244        right: m(SqlRef, {table: 'sched', id: data.sched.id}),
245      }),
246      ...extras,
247    ];
248
249    return m(Section, {title: 'Details'}, m(Tree, treeNodes));
250  }
251
252  goToThread(data: Data) {
253    if (data.sched.threadStateId) {
254      this.trace.selection.selectSqlEvent(
255        'thread_state',
256        data.sched.threadStateId,
257        {scrollToSelection: true},
258      );
259    }
260  }
261
262  renderCanvas() {}
263}
264