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