• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2024 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this 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 {Duration, duration, Time, time} from '../../base/time';
16import {LONG, NUM, STR_NULL} from '../../trace_processor/query_result';
17import m from 'mithril';
18import {DetailsShell} from '../../widgets/details_shell';
19import {GridLayout} from '../../widgets/grid_layout';
20import {Section} from '../../widgets/section';
21import {Tree, TreeNode} from '../../widgets/tree';
22import {Timestamp} from '../../components/widgets/timestamp';
23import {DurationWidget} from '../../components/widgets/duration';
24import {Anchor} from '../../widgets/anchor';
25import {Engine} from '../../trace_processor/engine';
26import {TrackEventDetailsPanel} from '../../public/details_panel';
27import {TrackEventSelection} from '../../public/selection';
28import {Trace} from '../../public/trace';
29import {ThreadMap} from '../dev.perfetto.Thread/threads';
30
31interface SuspendResumeEventDetails {
32  ts: time;
33  dur: duration;
34  utid: number;
35  cpu: number;
36  event_type: string;
37  device_name: string;
38  driver_name: string;
39  callback_phase: string;
40  thread_state_id: number;
41}
42
43export class SuspendResumeDetailsPanel implements TrackEventDetailsPanel {
44  private suspendResumeEventDetails?: SuspendResumeEventDetails;
45
46  constructor(
47    private readonly trace: Trace,
48    private readonly threads: ThreadMap,
49  ) {}
50
51  async load({eventId}: TrackEventSelection) {
52    this.suspendResumeEventDetails = await loadSuspendResumeEventDetails(
53      this.trace.engine,
54      eventId,
55    );
56  }
57
58  render() {
59    const eventDetails = this.suspendResumeEventDetails;
60    if (eventDetails) {
61      const threadInfo = this.threads.get(eventDetails.utid);
62      if (!threadInfo) {
63        return null;
64      }
65      return m(
66        DetailsShell,
67        {title: 'Suspend / Resume Event'},
68        m(
69          GridLayout,
70          m(
71            Section,
72            {title: 'Properties'},
73            m(
74              Tree,
75              m(TreeNode, {
76                left: 'Device Name',
77                right: eventDetails.device_name,
78              }),
79              m(TreeNode, {
80                left: 'Start time',
81                right: m(Timestamp, {ts: eventDetails.ts}),
82              }),
83              m(TreeNode, {
84                left: 'Duration',
85                right: m(DurationWidget, {dur: eventDetails.dur}),
86              }),
87              m(TreeNode, {
88                left: 'Driver Name',
89                right: eventDetails.driver_name,
90              }),
91              m(TreeNode, {
92                left: 'Callback Phase',
93                right: eventDetails.callback_phase,
94              }),
95              m(TreeNode, {
96                left: 'Thread',
97                right: m(
98                  Anchor,
99                  {
100                    icon: 'call_made',
101                    onclick: () => {
102                      this.goToThread(eventDetails.thread_state_id);
103                    },
104                  },
105                  `${threadInfo.threadName} [${threadInfo.tid}]`,
106                ),
107              }),
108              m(TreeNode, {left: 'CPU', right: eventDetails.cpu}),
109              m(TreeNode, {left: 'Event Type', right: eventDetails.event_type}),
110            ),
111          ),
112        ),
113      );
114    } else {
115      return m(DetailsShell, {
116        title: 'Suspend / Resume Event',
117        description: 'Loading...',
118      });
119    }
120  }
121
122  isLoading(): boolean {
123    return this.suspendResumeEventDetails === undefined;
124  }
125
126  goToThread(threadStateId: number) {
127    this.trace.selection.selectSqlEvent('thread_state', threadStateId, {
128      scrollToSelection: true,
129    });
130  }
131}
132
133async function loadSuspendResumeEventDetails(
134  engine: Engine,
135  id: number,
136): Promise<SuspendResumeEventDetails> {
137  const suspendResumeDetailsQuery = `
138    SELECT
139      ts,
140      dur,
141      EXTRACT_ARG(arg_set_id, 'utid') as utid,
142      EXTRACT_ARG(arg_set_id, 'cpu') as cpu,
143      EXTRACT_ARG(arg_set_id, 'event_type') as event_type,
144      EXTRACT_ARG(arg_set_id, 'device_name') as device_name,
145      EXTRACT_ARG(arg_set_id, 'driver_name') as driver_name,
146      EXTRACT_ARG(arg_set_id, 'callback_phase') as callback_phase
147    FROM slice
148    WHERE slice_id = ${id};
149  `;
150
151  const suspendResumeDetailsResult = await engine.query(
152    suspendResumeDetailsQuery,
153  );
154  const suspendResumeEventRow = suspendResumeDetailsResult.iter({
155    ts: LONG,
156    dur: LONG,
157    utid: NUM,
158    cpu: NUM,
159    event_type: STR_NULL,
160    device_name: STR_NULL,
161    driver_name: STR_NULL,
162    callback_phase: STR_NULL,
163  });
164  if (!suspendResumeEventRow.valid()) {
165    return {
166      ts: Time.fromRaw(0n),
167      dur: Duration.fromRaw(0n),
168      utid: 0,
169      cpu: 0,
170      event_type: 'Error',
171      device_name: 'Error',
172      driver_name: 'Error',
173      callback_phase: 'Error',
174      thread_state_id: 0,
175    };
176  }
177
178  const threadStateQuery = `
179    SELECT t.id as threadStateId
180    FROM thread_state t
181    WHERE
182      t.utid = ${suspendResumeEventRow.utid}
183      AND t.ts <= ${suspendResumeEventRow.ts}
184      AND t.ts + t.dur > ${suspendResumeEventRow.ts};
185  `;
186  const threadStateResult = await engine.query(threadStateQuery);
187  let threadStateId = 0;
188  if (threadStateResult.numRows() > 0) {
189    const threadStateRow = threadStateResult.firstRow({
190      threadStateId: NUM,
191    });
192    threadStateId = threadStateRow.threadStateId;
193  }
194
195  return {
196    ts: Time.fromRaw(suspendResumeEventRow.ts),
197    dur: Duration.fromRaw(suspendResumeEventRow.dur),
198    utid: suspendResumeEventRow.utid,
199    cpu: suspendResumeEventRow.cpu,
200    event_type:
201      suspendResumeEventRow.event_type !== null
202        ? suspendResumeEventRow.event_type
203        : 'N/A',
204    device_name:
205      suspendResumeEventRow.device_name !== null
206        ? suspendResumeEventRow.device_name
207        : 'N/A',
208    driver_name:
209      suspendResumeEventRow.driver_name !== null
210        ? suspendResumeEventRow.driver_name
211        : 'N/A',
212    callback_phase:
213      suspendResumeEventRow.callback_phase !== null
214        ? suspendResumeEventRow.callback_phase
215        : 'N/A',
216    thread_state_id: threadStateId,
217  };
218}
219