blob: 1c60d4f63bfd61d39df69fc6c446909e0250477e [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 m from 'mithril';
import {PageWithTraceAttrs} from '../../public/page';
import {Trace} from '../../public/trace';
import {
DurationColumn,
ProcessColumnSet,
StandardColumn,
ThreadColumnSet,
TimestampColumn,
} from '../../frontend/widgets/sql/table/well_known_columns';
import {SqlTableState} from '../../frontend/widgets/sql/table/state';
import {SqlTable} from '../../frontend/widgets/sql/table/table';
import {exists} from '../../base/utils';
import {Menu, MenuItem, MenuItemAttrs} from '../../widgets/menu';
import {
TableColumn,
TableColumnSet,
} from '../../frontend/widgets/sql/table/column';
import {Button} from '../../widgets/button';
import {Icons} from '../../base/semantic_icons';
import {DetailsShell} from '../../widgets/details_shell';
import {
Chart,
ChartOption,
createChartConfigFromSqlTableState,
renderChartComponent,
} from '../../frontend/widgets/charts/chart';
import {AddChartMenuItem} from '../../frontend/widgets/charts/add_chart_menu';
interface ExploreTableState {
sqlTableState?: SqlTableState;
selectedTable?: ExplorableTable;
}
interface ExplorableTable {
name: string;
module: string;
columns: (TableColumn | TableColumnSet)[];
}
export class ExplorePage implements m.ClassComponent<PageWithTraceAttrs> {
private readonly state: ExploreTableState;
private readonly charts: Chart[];
constructor() {
this.charts = [];
this.state = {};
}
// Show menu with standard library tables
private renderSelectableTablesMenuItems(
trace: Trace,
): m.Vnode<MenuItemAttrs, unknown>[] {
// TODO (lydiatse@): The following is purely for prototyping and
// should be derived from the actual stdlib itself rather than
// being hardcoded.
const explorableTables: ExplorableTable[] = [
{
name: 'android_binder_txns',
module: 'android.binder',
columns: [
new StandardColumn('aidl_name'),
new StandardColumn('aidl_ts'),
new StandardColumn('aidl_dur'),
new StandardColumn('binder_txn_id', {startsHidden: true}),
new ProcessColumnSet('client_upid', {title: 'client_upid'}),
new ThreadColumnSet('client_utid', {title: 'client_utid'}),
new StandardColumn('is_main_thread'),
new TimestampColumn('client_ts'),
new DurationColumn('client_dur'),
new StandardColumn('binder_reply_id', {startsHidden: true}),
new ProcessColumnSet('server_upid', {title: 'server_upid'}),
new ThreadColumnSet('server_utid', {title: 'server_utid'}),
new TimestampColumn('server_ts'),
new DurationColumn('server_dur'),
new StandardColumn('client_oom_score', {aggregationType: 'nominal'}),
new StandardColumn('server_oom_score', {aggregationType: 'nominal'}),
new StandardColumn('is_sync', {startsHidden: true}),
new StandardColumn('client_monotonic_dur', {startsHidden: true}),
new StandardColumn('server_monotonic_dur', {startsHidden: true}),
new StandardColumn('client_package_version_code', {
startsHidden: true,
}),
new StandardColumn('server_package_version_code', {
startsHidden: true,
}),
new StandardColumn('is_client_package_debuggable', {
startsHidden: true,
}),
new StandardColumn('is_server_package_debuggable', {
startsHidden: true,
}),
],
},
];
return explorableTables.map((table) => {
return m(MenuItem, {
label: table.name,
onclick: () => {
if (
this.state.selectedTable &&
table.name === this.state.selectedTable.name
) {
return;
}
this.state.selectedTable = table;
this.state.sqlTableState = new SqlTableState(
trace,
{
name: table.name,
columns: table.columns,
},
{imports: [table.module]},
);
},
});
});
}
private renderSqlTable() {
const sqlTableState = this.state.sqlTableState;
if (sqlTableState === undefined) return;
const range = sqlTableState.getDisplayedRange();
const rowCount = sqlTableState.getTotalRowCount();
const navigation = [
exists(range) &&
exists(rowCount) &&
`Showing rows ${range.from}-${range.to} of ${rowCount}`,
m(Button, {
icon: Icons.GoBack,
disabled: !sqlTableState.canGoBack(),
onclick: () => sqlTableState!.goBack(),
}),
m(Button, {
icon: Icons.GoForward,
disabled: !sqlTableState.canGoForward(),
onclick: () => sqlTableState!.goForward(),
}),
];
return m(
DetailsShell,
{
title: 'Explore Table',
buttons: navigation,
fillParent: false,
},
m(SqlTable, {
state: sqlTableState,
addColumnMenuItems: (column, columnAlias) =>
m(AddChartMenuItem, {
chartConfig: createChartConfigFromSqlTableState(
column,
columnAlias,
sqlTableState,
),
chartOptions: [ChartOption.HISTOGRAM],
addChart: (chart) => this.charts.push(chart),
}),
}),
);
}
view({attrs}: m.CVnode<PageWithTraceAttrs>) {
return m(
'.explore-page',
m(Menu, this.renderSelectableTablesMenuItems(attrs.trace)),
this.charts.map((chart) => renderChartComponent(chart)),
this.state.selectedTable && this.renderSqlTable(),
);
}
}