blob: d43f6997d24108b21f3b66231d8dc7942c60f09a [file] [log] [blame]
// Copyright (C) 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {Duration, duration, Time, time} from '../../base/time';
import {LONG, NUM, STR_NULL} from '../../trace_processor/query_result';
import m from 'mithril';
import {DetailsShell} from '../../widgets/details_shell';
import {GridLayout} from '../../widgets/grid_layout';
import {Section} from '../../widgets/section';
import {Tree, TreeNode} from '../../widgets/tree';
import {Timestamp} from '../../public/lib/widgets/timestamp';
import {DurationWidget} from '../../public/lib/widgets/duration';
import {Anchor} from '../../widgets/anchor';
import {Engine} from '../../trace_processor/engine';
import {TrackEventDetailsPanel} from '../../public/details_panel';
import {TrackEventSelection} from '../../public/selection';
import {Trace} from '../../public/trace';
import {ThreadMap} from '../dev.perfetto.Thread/threads';
interface SuspendResumeEventDetails {
ts: time;
dur: duration;
utid: number;
cpu: number;
event_type: string;
device_name: string;
driver_name: string;
callback_phase: string;
thread_state_id: number;
}
export class SuspendResumeDetailsPanel implements TrackEventDetailsPanel {
private suspendResumeEventDetails?: SuspendResumeEventDetails;
constructor(
private readonly trace: Trace,
private readonly threads: ThreadMap,
) {}
async load({eventId}: TrackEventSelection) {
this.suspendResumeEventDetails = await loadSuspendResumeEventDetails(
this.trace.engine,
eventId,
);
}
render() {
const eventDetails = this.suspendResumeEventDetails;
if (eventDetails) {
const threadInfo = this.threads.get(eventDetails.utid);
if (!threadInfo) {
return null;
}
return m(
DetailsShell,
{title: 'Suspend / Resume Event'},
m(
GridLayout,
m(
Section,
{title: 'Properties'},
m(
Tree,
m(TreeNode, {
left: 'Device Name',
right: eventDetails.device_name,
}),
m(TreeNode, {
left: 'Start time',
right: m(Timestamp, {ts: eventDetails.ts}),
}),
m(TreeNode, {
left: 'Duration',
right: m(DurationWidget, {dur: eventDetails.dur}),
}),
m(TreeNode, {
left: 'Driver Name',
right: eventDetails.driver_name,
}),
m(TreeNode, {
left: 'Callback Phase',
right: eventDetails.callback_phase,
}),
m(TreeNode, {
left: 'Thread',
right: m(
Anchor,
{
icon: 'call_made',
onclick: () => {
this.goToThread(eventDetails.thread_state_id);
},
},
`${threadInfo.threadName} [${threadInfo.tid}]`,
),
}),
m(TreeNode, {left: 'CPU', right: eventDetails.cpu}),
m(TreeNode, {left: 'Event Type', right: eventDetails.event_type}),
),
),
),
);
} else {
return m(DetailsShell, {
title: 'Suspend / Resume Event',
description: 'Loading...',
});
}
}
isLoading(): boolean {
return this.suspendResumeEventDetails === undefined;
}
goToThread(threadStateId: number) {
this.trace.selection.selectSqlEvent('thread_state', threadStateId, {
scrollToSelection: true,
});
}
}
async function loadSuspendResumeEventDetails(
engine: Engine,
id: number,
): Promise<SuspendResumeEventDetails> {
const suspendResumeDetailsQuery = `
SELECT ts,
dur,
EXTRACT_ARG(arg_set_id, 'utid') as utid,
EXTRACT_ARG(arg_set_id, 'ucpu') as ucpu,
EXTRACT_ARG(arg_set_id, 'event_type') as event_type,
EXTRACT_ARG(arg_set_id, 'device_name') as device_name,
EXTRACT_ARG(arg_set_id, 'driver_name') as driver_name,
EXTRACT_ARG(arg_set_id, 'callback_phase') as callback_phase
FROM slice
WHERE slice_id = ${id};
`;
const suspendResumeDetailsResult = await engine.query(
suspendResumeDetailsQuery,
);
const suspendResumeEventRow = suspendResumeDetailsResult.iter({
ts: LONG,
dur: LONG,
utid: NUM,
ucpu: NUM,
event_type: STR_NULL,
device_name: STR_NULL,
driver_name: STR_NULL,
callback_phase: STR_NULL,
});
if (!suspendResumeEventRow.valid()) {
return {
ts: Time.fromRaw(0n),
dur: Duration.fromRaw(0n),
utid: 0,
cpu: 0,
event_type: 'Error',
device_name: 'Error',
driver_name: 'Error',
callback_phase: 'Error',
thread_state_id: 0,
};
}
const threadStateQuery = `
SELECT t.id as threadStateId
FROM thread_state t
WHERE t.utid = ${suspendResumeEventRow.utid}
AND t.ts <= ${suspendResumeEventRow.ts}
AND t.ts + t.dur > ${suspendResumeEventRow.ts};
`;
const threadStateResult = await engine.query(threadStateQuery);
let threadStateId = 0;
if (threadStateResult.numRows() > 0) {
const threadStateRow = threadStateResult.firstRow({
threadStateId: NUM,
});
threadStateId = threadStateRow.threadStateId;
}
const cpuQuery = `
SELECT cpu
FROM cpu
WHERE cpu.id = ${suspendResumeEventRow.ucpu}
`;
const cpuResult = await engine.query(cpuQuery);
let cpu = 0;
if (cpuResult.numRows() > 0) {
const cpuRow = cpuResult.firstRow({
cpu: NUM,
});
cpu = cpuRow.cpu;
}
return {
ts: Time.fromRaw(suspendResumeEventRow.ts),
dur: Duration.fromRaw(suspendResumeEventRow.dur),
utid: suspendResumeEventRow.utid,
cpu: cpu,
event_type:
suspendResumeEventRow.event_type !== null
? suspendResumeEventRow.event_type
: 'N/A',
device_name:
suspendResumeEventRow.device_name !== null
? suspendResumeEventRow.device_name
: 'N/A',
driver_name:
suspendResumeEventRow.driver_name !== null
? suspendResumeEventRow.driver_name
: 'N/A',
callback_phase:
suspendResumeEventRow.callback_phase !== null
? suspendResumeEventRow.callback_phase
: 'N/A',
thread_state_id: threadStateId,
};
}