blob: 39e642708e7af3021bc0ad9e1c204d087ec182b9 [file] [log] [blame]
// Copyright (C) 2023 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 m from 'mithril';
import {BigintMath} from '../../base/bigint_math';
import {exists} from '../../base/utils';
import {Actions} from '../../common/actions';
import {EngineProxy} from '../../common/engine';
import {
LONG,
LONG_NULL,
NUM,
STR,
STR_NULL,
} from '../../common/query_result';
import {TPDuration, TPTime} from '../../common/time';
import {Anchor} from '../anchor';
import {globals} from '../globals';
import {focusHorizontalRange, verticalScrollToTrack} from '../scroll_helper';
import {Icons} from '../semantic_icons';
import {
asArgSetId,
asSliceSqlId,
asTPTimestamp,
asUpid,
asUtid,
SliceSqlId,
TPTimestamp,
Upid,
Utid,
} from '../sql_types';
import {constraintsToQuerySuffix, SQLConstraints} from '../sql_utils';
import {
getProcessInfo,
getThreadInfo,
ProcessInfo,
ThreadInfo,
} from '../thread_and_process_info';
import {Arg, getArgs} from './args';
export interface SliceDetails {
id: SliceSqlId;
name: string;
ts: TPTimestamp;
absTime?: string;
dur: TPDuration;
sqlTrackId: number;
thread?: ThreadInfo;
process?: ProcessInfo;
threadTs?: TPTime;
threadDur?: TPDuration;
category?: string;
args?: Arg[];
}
async function getUtidAndUpid(engine: EngineProxy, sqlTrackId: number):
Promise<{utid?: Utid, upid?: Upid}> {
const columnInfo = (await engine.query(`
WITH
leafTrackTable AS (SELECT type FROM track WHERE id = ${sqlTrackId}),
cols AS (
SELECT name
FROM pragma_table_info((SELECT type FROM leafTrackTable))
)
SELECT
type as leafTrackTable,
'upid' in cols AS hasUpid,
'utid' in cols AS hasUtid
FROM leafTrackTable
`)).firstRow({hasUpid: NUM, hasUtid: NUM, leafTrackTable: STR});
const hasUpid = columnInfo.hasUpid !== 0;
const hasUtid = columnInfo.hasUtid !== 0;
const result: {utid?: Utid, upid?: Upid} = {};
if (hasUtid) {
const utid = (await engine.query(`
SELECT utid
FROM ${columnInfo.leafTrackTable}
WHERE id = ${sqlTrackId};
`)).firstRow({
utid: NUM,
}).utid;
result.utid = asUtid(utid);
} else if (hasUpid) {
const upid = (await engine.query(`
SELECT upid
FROM ${columnInfo.leafTrackTable}
WHERE id = ${sqlTrackId};
`)).firstRow({
upid: NUM,
}).upid;
result.upid = asUpid(upid);
}
return result;
}
async function getSliceFromConstraints(
engine: EngineProxy, constraints: SQLConstraints): Promise<SliceDetails[]> {
const query = await engine.query(`
SELECT
id,
name,
ts,
dur,
track_id as trackId,
thread_dur as threadDur,
thread_ts as threadTs,
category,
arg_set_id as argSetId,
ABS_TIME_STR(ts) as absTime
FROM slice
${constraintsToQuerySuffix(constraints)}`);
const it = query.iter({
id: NUM,
name: STR,
ts: LONG,
dur: LONG,
trackId: NUM,
threadDur: LONG_NULL,
threadTs: LONG_NULL,
category: STR_NULL,
argSetId: NUM,
absTime: STR_NULL,
});
const result: SliceDetails[] = [];
for (; it.valid(); it.next()) {
const {utid, upid} = await getUtidAndUpid(engine, it.trackId);
const thread: ThreadInfo|undefined =
utid === undefined ? undefined : await getThreadInfo(engine, utid);
const process: ProcessInfo|undefined = thread !== undefined ?
thread.process :
(upid === undefined ? undefined : await getProcessInfo(engine, upid));
result.push({
id: asSliceSqlId(it.id),
name: it.name,
ts: asTPTimestamp(it.ts),
dur: it.dur,
sqlTrackId: it.trackId,
thread,
process,
threadDur: it.threadDur ?? undefined,
threadTs: exists(it.threadTs) ? it.threadTs : undefined,
category: it.category ?? undefined,
args: await getArgs(engine, asArgSetId(it.argSetId)),
absTime: it.absTime ?? undefined,
});
}
return result;
}
export async function getSlice(
engine: EngineProxy, id: SliceSqlId): Promise<SliceDetails|undefined> {
const result = await getSliceFromConstraints(engine, {
filters: [`id=${id}`],
});
if (result.length > 1) {
throw new Error(`slice table has more than one row with id ${id}`);
}
if (result.length === 0) {
return undefined;
}
return result[0];
}
interface SliceRefAttrs {
readonly id: SliceSqlId;
readonly name: string;
readonly ts: TPTimestamp;
readonly dur: TPDuration;
readonly sqlTrackId: number;
// Whether clicking on the reference should change the current tab
// to "current selection" tab in addition to updating the selection
// and changing the viewport. True by default.
readonly switchToCurrentSelectionTab?: boolean;
}
export class SliceRef implements m.ClassComponent<SliceRefAttrs> {
view(vnode: m.Vnode<SliceRefAttrs>) {
const switchTab = vnode.attrs.switchToCurrentSelectionTab ?? true;
return m(
Anchor,
{
icon: Icons.UpdateSelection,
onclick: () => {
const uiTrackId =
globals.state.uiTrackIdByTraceTrackId[vnode.attrs.sqlTrackId];
if (uiTrackId === undefined) return;
verticalScrollToTrack(uiTrackId, true);
// Clamp duration to 1 - i.e. for instant events
const dur = BigintMath.max(1n, vnode.attrs.dur);
focusHorizontalRange(vnode.attrs.ts, vnode.attrs.ts + dur);
globals.makeSelection(
Actions.selectChromeSlice(
{id: vnode.attrs.id, trackId: uiTrackId, table: 'slice'}),
switchTab ? 'current_selection' : null);
},
},
vnode.attrs.name);
}
}
export function sliceRef(slice: SliceDetails, name?: string): m.Child {
return m(SliceRef, {
id: slice.id,
name: name ?? slice.name,
ts: slice.ts,
dur: slice.dur,
sqlTrackId: slice.sqlTrackId,
});
}