1// Copyright (C) 2024 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 {DetailsShell} from '../../../widgets/details_shell'; 18import {uuidv4} from '../../../base/uuid'; 19import {BottomTab, NewBottomTabArgs} from '../../bottom_tab'; 20import {VegaView} from '../../../widgets/vega_view'; 21import {addEphemeralTab} from '../../../common/addEphemeralTab'; 22import {HistogramState} from './state'; 23import {stringifyJsonWithBigints} from '../../../base/json_utils'; 24import {Engine} from '../../../public'; 25import {Filter} from '../../sql_table/state'; 26import {isString} from '../../../base/object_utils'; 27 28interface HistogramTabConfig { 29 columnTitle: string; // Human readable column name (ex: Duration) 30 sqlColumn: string; // SQL column name (ex: dur) 31 filters?: Filter[]; // Filters applied to SQL table 32 tableDisplay?: string; // Human readable table name (ex: slices) 33 query: string; // SQL query for the underlying data 34} 35 36export function addHistogramTab( 37 config: HistogramTabConfig, 38 engine: Engine, 39): void { 40 const histogramTab = new HistogramTab({ 41 config, 42 engine, 43 uuid: uuidv4(), 44 }); 45 46 addEphemeralTab(histogramTab, 'histogramTab'); 47} 48 49export class HistogramTab extends BottomTab<HistogramTabConfig> { 50 static readonly kind = 'dev.perfetto.HistogramTab'; 51 52 private state: HistogramState; 53 54 constructor(args: NewBottomTabArgs<HistogramTabConfig>) { 55 super(args); 56 57 this.state = new HistogramState( 58 this.engine, 59 this.config.query, 60 this.config.sqlColumn, 61 ); 62 } 63 64 static create(args: NewBottomTabArgs<HistogramTabConfig>): HistogramTab { 65 return new HistogramTab(args); 66 } 67 68 viewTab() { 69 return m( 70 DetailsShell, 71 { 72 title: this.getTitle(), 73 description: this.getDescription(), 74 }, 75 m( 76 '.histogram', 77 m(VegaView, { 78 spec: ` 79 { 80 "$schema": "https://vega.github.io/schema/vega-lite/v5.json", 81 "mark": "bar", 82 "data": { 83 "values": ${ 84 this.state.data 85 ? stringifyJsonWithBigints(this.state.data) 86 : [] 87 } 88 }, 89 "encoding": { 90 "${this.state.chartConfig.binAxis}": { 91 "bin": ${this.state.chartConfig.isBinned}, 92 "field": "${this.config.sqlColumn}", 93 "type": "${this.state.chartConfig.binAxisType}", 94 "title": "${this.config.columnTitle}", 95 "sort": ${this.state.chartConfig.sort}, 96 "axis": { 97 "labelLimit": ${this.state.chartConfig.labelLimit} 98 } 99 }, 100 "${this.state.chartConfig.countAxis}": { 101 "aggregate": "count", 102 "title": "Count" 103 } 104 } 105 } 106 `, 107 data: {}, 108 }), 109 ), 110 ); 111 } 112 113 getTitle(): string { 114 return `${this.toTitleCase(this.config.columnTitle)} ${ 115 this.state.chartConfig.binAxisType === 'quantitative' 116 ? 'Histogram' 117 : 'Counts' 118 }`; 119 } 120 121 getDescription(): string { 122 let desc = `Count distribution for ${ 123 this.config.tableDisplay ? this.config.tableDisplay : '' 124 } table`; 125 126 if (this.config.filters) { 127 const filterStrings: string[] = []; 128 desc += ' where '; 129 130 for (const f of this.config.filters) { 131 filterStrings.push(`${isString(f) ? f : `${f.argName} ${f.op}`}`); 132 } 133 134 desc += filterStrings.join(', '); 135 } 136 137 return desc; 138 } 139 140 toTitleCase(s: string): string { 141 const words = s.split(/\s/); 142 143 for (let i = 0; i < words.length; ++i) { 144 words[i] = words[i][0].toUpperCase() + words[i].substring(1); 145 } 146 147 return words.join(' '); 148 } 149 150 isLoading(): boolean { 151 return this.state.isLoading; 152 } 153} 154