• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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