• 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';
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