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