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'; 16import {findRef} from '../../base/dom_utils'; 17import {assertUnreachable} from '../../base/logging'; 18import {Trace} from '../../public/trace'; 19import {Form, FormLabel} from '../../widgets/form'; 20import {Select} from '../../widgets/select'; 21import {TextInput} from '../../widgets/text_input'; 22import {addDebugCounterTrack, addDebugSliceTrack} from './debug_tracks'; 23import {SqlDataSource} from './query_slice_track'; 24 25interface AddDebugTrackMenuAttrs { 26 dataSource: Required<SqlDataSource>; 27 trace: Trace; 28} 29 30const TRACK_NAME_FIELD_REF = 'TRACK_NAME_FIELD'; 31 32export class AddDebugTrackMenu 33 implements m.ClassComponent<AddDebugTrackMenuAttrs> 34{ 35 readonly columns: string[]; 36 37 name: string = ''; 38 trackType: 'slice' | 'counter' = 'slice'; 39 // Names of columns which will be used as data sources for rendering. 40 // We store the config for all possible columns used for rendering (i.e. 41 // 'value' for slice and 'name' for counter) and then just don't the values 42 // which don't match the currently selected track type (so changing track type 43 // from A to B and back to A is a no-op). 44 renderParams: { 45 ts: string; 46 dur: string; 47 name: string; 48 value: string; 49 pivot: string; 50 }; 51 52 constructor(vnode: m.Vnode<AddDebugTrackMenuAttrs>) { 53 this.columns = [...vnode.attrs.dataSource.columns]; 54 55 const chooseDefaultOption = (name: string) => { 56 for (const column of this.columns) { 57 if (column === name) return column; 58 } 59 for (const column of this.columns) { 60 if (column.endsWith(`_${name}`)) return column; 61 } 62 // Debug tracks support data without dur, in which case it's treated as 63 // 0. 64 if (name === 'dur') { 65 return '0'; 66 } 67 return this.columns[0]; 68 }; 69 70 this.renderParams = { 71 ts: chooseDefaultOption('ts'), 72 dur: chooseDefaultOption('dur'), 73 name: chooseDefaultOption('name'), 74 value: chooseDefaultOption('value'), 75 pivot: '', 76 }; 77 } 78 79 oncreate({dom}: m.VnodeDOM<AddDebugTrackMenuAttrs>) { 80 this.focusTrackNameField(dom); 81 } 82 83 private focusTrackNameField(dom: Element) { 84 const element = findRef(dom, TRACK_NAME_FIELD_REF); 85 if (element) { 86 if (element instanceof HTMLInputElement) { 87 element.focus(); 88 } 89 } 90 } 91 92 private renderTrackTypeSelect() { 93 const options = []; 94 for (const type of ['slice', 'counter']) { 95 options.push( 96 m( 97 'option', 98 { 99 value: type, 100 selected: this.trackType === type ? true : undefined, 101 }, 102 type, 103 ), 104 ); 105 } 106 return m( 107 Select, 108 { 109 id: 'track_type', 110 oninput: (e: Event) => { 111 if (!e.target) return; 112 this.trackType = (e.target as HTMLSelectElement).value as 113 | 'slice' 114 | 'counter'; 115 }, 116 }, 117 options, 118 ); 119 } 120 121 view(vnode: m.Vnode<AddDebugTrackMenuAttrs>) { 122 const renderSelect = (name: 'ts' | 'dur' | 'name' | 'value' | 'pivot') => { 123 const options = []; 124 125 if (name === 'pivot') { 126 options.push( 127 m( 128 'option', 129 {selected: this.renderParams[name] === '' ? true : undefined}, 130 m('i', ''), 131 ), 132 ); 133 } 134 for (const column of this.columns) { 135 options.push( 136 m( 137 'option', 138 {selected: this.renderParams[name] === column ? true : undefined}, 139 column, 140 ), 141 ); 142 } 143 if (name === 'dur') { 144 options.push( 145 m( 146 'option', 147 {selected: this.renderParams[name] === '0' ? true : undefined}, 148 m('i', '0'), 149 ), 150 ); 151 } 152 return [ 153 m(FormLabel, {for: name}, name), 154 m( 155 Select, 156 { 157 id: name, 158 oninput: (e: Event) => { 159 if (!e.target) return; 160 this.renderParams[name] = (e.target as HTMLSelectElement).value; 161 }, 162 }, 163 options, 164 ), 165 ]; 166 }; 167 168 return m( 169 Form, 170 { 171 onSubmit: () => this.createDebugTracks(vnode.attrs), 172 submitLabel: 'Show', 173 }, 174 m(FormLabel, {for: 'track_name'}, 'Track name'), 175 m(TextInput, { 176 id: 'track_name', 177 ref: TRACK_NAME_FIELD_REF, 178 onkeydown: (e: KeyboardEvent) => { 179 // Allow Esc to close popup. 180 if (e.key === 'Escape') return; 181 }, 182 oninput: (e: KeyboardEvent) => { 183 if (!e.target) return; 184 this.name = (e.target as HTMLInputElement).value; 185 }, 186 }), 187 m(FormLabel, {for: 'track_type'}, 'Track type'), 188 this.renderTrackTypeSelect(), 189 renderSelect('ts'), 190 this.trackType === 'slice' && renderSelect('dur'), 191 this.trackType === 'slice' && renderSelect('name'), 192 this.trackType === 'counter' && renderSelect('value'), 193 renderSelect('pivot'), 194 ); 195 } 196 197 private createDebugTracks(attrs: AddDebugTrackMenuAttrs) { 198 switch (this.trackType) { 199 case 'slice': 200 addDebugSliceTrack({ 201 trace: attrs.trace, 202 data: attrs.dataSource, 203 title: this.name, 204 columns: { 205 ts: this.renderParams.ts, 206 dur: this.renderParams.dur, 207 name: this.renderParams.name, 208 }, 209 argColumns: this.columns, 210 pivotOn: this.renderParams.pivot, 211 }); 212 break; 213 case 'counter': 214 addDebugCounterTrack({ 215 trace: attrs.trace, 216 data: attrs.dataSource, 217 title: this.name, 218 columns: { 219 ts: this.renderParams.ts, 220 value: this.renderParams.value, 221 }, 222 pivotOn: this.renderParams.pivot, 223 }); 224 break; 225 default: 226 assertUnreachable(this.trackType); 227 } 228 } 229} 230