blob: 4312079966c9f4e48333701cfa9cb2ed3ea302d7 [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 {duration, Time, time} from '../../base/time';
import {BottomTab, NewBottomTabArgs} from '../../frontend/bottom_tab';
import {GenericSliceDetailsTabConfig} from '../../frontend/generic_slice_details_tab';
import {DurationWidget} from '../../frontend/widgets/duration';
import {Timestamp} from '../../frontend/widgets/timestamp';
import {LONG, NUM, STR, STR_NULL} from '../../trace_processor/query_result';
import {DetailsShell} from '../../widgets/details_shell';
import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
import {Section} from '../../widgets/section';
import {SqlRef} from '../../widgets/sql_ref';
import {dictToTreeNodes, Tree} from '../../widgets/tree';
import {asUpid, Upid} from '../../frontend/sql_types';
interface Data {
startupId: number;
eventName: string;
startupBeginTs: time;
durToFirstVisibleContent: duration;
launchCause?: string;
upid: Upid;
}
export class StartupDetailsPanel extends BottomTab<GenericSliceDetailsTabConfig> {
static readonly kind = 'org.perfetto.StartupDetailsPanel';
private loaded = false;
private data: Data | undefined;
static create(
args: NewBottomTabArgs<GenericSliceDetailsTabConfig>,
): StartupDetailsPanel {
return new StartupDetailsPanel(args);
}
constructor(args: NewBottomTabArgs<GenericSliceDetailsTabConfig>) {
super(args);
this.loadData();
}
private async loadData() {
const queryResult = await this.engine.query(`
SELECT
activity_id AS startupId,
name,
startup_begin_ts AS startupBeginTs,
CASE
WHEN first_visible_content_ts IS NULL THEN 0
ELSE first_visible_content_ts - startup_begin_ts
END AS durTofirstVisibleContent,
launch_cause AS launchCause,
browser_upid AS upid
FROM chrome_startups
WHERE id = ${this.config.id};
`);
const iter = queryResult.firstRow({
startupId: NUM,
name: STR,
startupBeginTs: LONG,
durTofirstVisibleContent: LONG,
launchCause: STR_NULL,
upid: NUM,
});
this.data = {
startupId: iter.startupId,
eventName: iter.name,
startupBeginTs: Time.fromRaw(iter.startupBeginTs),
durToFirstVisibleContent: iter.durTofirstVisibleContent,
upid: asUpid(iter.upid),
};
if (iter.launchCause) {
this.data.launchCause = iter.launchCause;
}
this.loaded = true;
}
private getDetailsDictionary() {
const details: {[key: string]: m.Child} = {};
if (this.data === undefined) return details;
details['Activity ID'] = this.data.startupId;
details['Browser Upid'] = this.data.upid;
details['Startup Event'] = this.data.eventName;
details['Startup Timestamp'] = m(Timestamp, {ts: this.data.startupBeginTs});
details['Duration to First Visible Content'] = m(DurationWidget, {
dur: this.data.durToFirstVisibleContent,
});
if (this.data.launchCause) {
details['Launch Cause'] = this.data.launchCause;
}
details['SQL ID'] = m(SqlRef, {
table: 'chrome_startups',
id: this.config.id,
});
return details;
}
viewTab() {
if (this.isLoading()) {
return m('h2', 'Loading');
}
return m(
DetailsShell,
{
title: this.getTitle(),
},
m(
GridLayout,
m(
GridLayoutColumn,
m(
Section,
{title: 'Details'},
m(Tree, dictToTreeNodes(this.getDetailsDictionary())),
),
),
),
);
}
getTitle(): string {
return this.config.title;
}
isLoading() {
return !this.loaded;
}
}