1// Copyright (C) 2023 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 m from 'mithril'; 16 17import {ColumnType} from '../../common/query_result'; 18import {tpDurationFromSql, tpTimeFromSql} from '../../common/time'; 19import { 20 BottomTab, 21 bottomTabRegistry, 22 NewBottomTabArgs, 23} from '../../frontend/bottom_tab'; 24import {globals} from '../../frontend/globals'; 25import {asTPTimestamp} from '../../frontend/sql_types'; 26import {Duration} from '../../frontend/widgets/duration'; 27import {Timestamp} from '../../frontend/widgets/timestamp'; 28import {dictToTree} from '../../frontend/widgets/tree'; 29 30import {ARG_PREFIX} from './add_debug_track_menu'; 31 32interface DebugSliceDetailsTabConfig { 33 sqlTableName: string; 34 id: number; 35} 36 37function SqlValueToString(val: ColumnType) { 38 if (val instanceof Uint8Array) { 39 return `<blob length=${val.length}>`; 40 } 41 if (val === null) { 42 return 'NULL'; 43 } 44 return val.toString(); 45} 46 47export class DebugSliceDetailsTab extends 48 BottomTab<DebugSliceDetailsTabConfig> { 49 static readonly kind = 'org.perfetto.DebugSliceDetailsTab'; 50 51 data: {[key: string]: ColumnType}|undefined; 52 53 static create(args: NewBottomTabArgs): DebugSliceDetailsTab { 54 return new DebugSliceDetailsTab(args); 55 } 56 57 constructor(args: NewBottomTabArgs) { 58 super(args); 59 60 this.engine 61 .query(`select * from ${this.config.sqlTableName} where id = ${ 62 this.config.id}`) 63 .then((queryResult) => { 64 this.data = queryResult.firstRow({}); 65 globals.rafScheduler.scheduleFullRedraw(); 66 }); 67 } 68 69 viewTab() { 70 if (this.data === undefined) { 71 return m('h2', 'Loading'); 72 } 73 const left = dictToTree({ 74 'Name': this.data['name'] as string, 75 'Start time': 76 m(Timestamp, {ts: asTPTimestamp(tpTimeFromSql(this.data['ts']))}), 77 'Duration': m(Duration, {dur: tpDurationFromSql(this.data['dur'])}), 78 'Debug slice id': `${this.config.sqlTableName}[${this.config.id}]`, 79 }); 80 const args: {[key: string]: m.Child} = {}; 81 for (const key of Object.keys(this.data)) { 82 if (key.startsWith(ARG_PREFIX)) { 83 args[key.substr(ARG_PREFIX.length)] = SqlValueToString(this.data[key]); 84 } 85 } 86 return m( 87 '.details-panel', 88 m('header.overview', m('span', 'Debug Slice')), 89 m('.details-table-multicolumn', 90 { 91 style: { 92 'user-select': 'text', 93 }, 94 }, 95 m('.half-width-panel', left), 96 m('.half-width-panel', dictToTree(args)))); 97 } 98 99 getTitle(): string { 100 return `Current Selection`; 101 } 102 103 isLoading() { 104 return this.data === undefined; 105 } 106 107 renderTabCanvas() { 108 return; 109 } 110} 111 112bottomTabRegistry.register(DebugSliceDetailsTab); 113