xref: /aosp_15_r20/external/perfetto/ui/src/frontend/aggregation_panel.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1*6dbdd20aSAndroid Build Coastguard Worker// Copyright (C) 2019 The Android Open Source Project
2*6dbdd20aSAndroid Build Coastguard Worker//
3*6dbdd20aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License");
4*6dbdd20aSAndroid Build Coastguard Worker// you may not use size file except in compliance with the License.
5*6dbdd20aSAndroid Build Coastguard Worker// You may obtain a copy of the License at
6*6dbdd20aSAndroid Build Coastguard Worker//
7*6dbdd20aSAndroid Build Coastguard Worker//      http://www.apache.org/licenses/LICENSE-2.0
8*6dbdd20aSAndroid Build Coastguard Worker//
9*6dbdd20aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*6dbdd20aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS,
11*6dbdd20aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*6dbdd20aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and
13*6dbdd20aSAndroid Build Coastguard Worker// limitations under the License.
14*6dbdd20aSAndroid Build Coastguard Worker
15*6dbdd20aSAndroid Build Coastguard Workerimport m from 'mithril';
16*6dbdd20aSAndroid Build Coastguard Workerimport {
17*6dbdd20aSAndroid Build Coastguard Worker  AggregateData,
18*6dbdd20aSAndroid Build Coastguard Worker  Column,
19*6dbdd20aSAndroid Build Coastguard Worker  ThreadStateExtra,
20*6dbdd20aSAndroid Build Coastguard Worker  isEmptyData,
21*6dbdd20aSAndroid Build Coastguard Worker} from '../public/aggregation';
22*6dbdd20aSAndroid Build Coastguard Workerimport {colorForState} from '../components/colorizer';
23*6dbdd20aSAndroid Build Coastguard Workerimport {DurationWidget} from '../components/widgets/duration';
24*6dbdd20aSAndroid Build Coastguard Workerimport {EmptyState} from '../widgets/empty_state';
25*6dbdd20aSAndroid Build Coastguard Workerimport {Anchor} from '../widgets/anchor';
26*6dbdd20aSAndroid Build Coastguard Workerimport {Icons} from '../base/semantic_icons';
27*6dbdd20aSAndroid Build Coastguard Workerimport {translateState} from '../components/sql_utils/thread_state';
28*6dbdd20aSAndroid Build Coastguard Workerimport {TraceImpl} from '../core/trace_impl';
29*6dbdd20aSAndroid Build Coastguard Worker
30*6dbdd20aSAndroid Build Coastguard Workerexport interface AggregationPanelAttrs {
31*6dbdd20aSAndroid Build Coastguard Worker  data?: AggregateData;
32*6dbdd20aSAndroid Build Coastguard Worker  aggregatorId: string;
33*6dbdd20aSAndroid Build Coastguard Worker  trace: TraceImpl;
34*6dbdd20aSAndroid Build Coastguard Worker}
35*6dbdd20aSAndroid Build Coastguard Worker
36*6dbdd20aSAndroid Build Coastguard Workerexport class AggregationPanel
37*6dbdd20aSAndroid Build Coastguard Worker  implements m.ClassComponent<AggregationPanelAttrs>
38*6dbdd20aSAndroid Build Coastguard Worker{
39*6dbdd20aSAndroid Build Coastguard Worker  private trace: TraceImpl;
40*6dbdd20aSAndroid Build Coastguard Worker
41*6dbdd20aSAndroid Build Coastguard Worker  constructor({attrs}: m.CVnode<AggregationPanelAttrs>) {
42*6dbdd20aSAndroid Build Coastguard Worker    this.trace = attrs.trace;
43*6dbdd20aSAndroid Build Coastguard Worker  }
44*6dbdd20aSAndroid Build Coastguard Worker
45*6dbdd20aSAndroid Build Coastguard Worker  view({attrs}: m.CVnode<AggregationPanelAttrs>) {
46*6dbdd20aSAndroid Build Coastguard Worker    if (!attrs.data || isEmptyData(attrs.data)) {
47*6dbdd20aSAndroid Build Coastguard Worker      return m(
48*6dbdd20aSAndroid Build Coastguard Worker        EmptyState,
49*6dbdd20aSAndroid Build Coastguard Worker        {
50*6dbdd20aSAndroid Build Coastguard Worker          className: 'pf-noselection',
51*6dbdd20aSAndroid Build Coastguard Worker          title: 'No relevant tracks in selection',
52*6dbdd20aSAndroid Build Coastguard Worker        },
53*6dbdd20aSAndroid Build Coastguard Worker        m(
54*6dbdd20aSAndroid Build Coastguard Worker          Anchor,
55*6dbdd20aSAndroid Build Coastguard Worker          {
56*6dbdd20aSAndroid Build Coastguard Worker            icon: Icons.ChangeTab,
57*6dbdd20aSAndroid Build Coastguard Worker            onclick: () => {
58*6dbdd20aSAndroid Build Coastguard Worker              this.trace.tabs.showCurrentSelectionTab();
59*6dbdd20aSAndroid Build Coastguard Worker            },
60*6dbdd20aSAndroid Build Coastguard Worker          },
61*6dbdd20aSAndroid Build Coastguard Worker          'Go to current selection tab',
62*6dbdd20aSAndroid Build Coastguard Worker        ),
63*6dbdd20aSAndroid Build Coastguard Worker      );
64*6dbdd20aSAndroid Build Coastguard Worker    }
65*6dbdd20aSAndroid Build Coastguard Worker
66*6dbdd20aSAndroid Build Coastguard Worker    return m(
67*6dbdd20aSAndroid Build Coastguard Worker      '.details-panel',
68*6dbdd20aSAndroid Build Coastguard Worker      m(
69*6dbdd20aSAndroid Build Coastguard Worker        '.details-panel-heading.aggregation',
70*6dbdd20aSAndroid Build Coastguard Worker        attrs.data.extra !== undefined &&
71*6dbdd20aSAndroid Build Coastguard Worker          attrs.data.extra.kind === 'THREAD_STATE'
72*6dbdd20aSAndroid Build Coastguard Worker          ? this.showStateSummary(attrs.data.extra)
73*6dbdd20aSAndroid Build Coastguard Worker          : null,
74*6dbdd20aSAndroid Build Coastguard Worker        this.showTimeRange(),
75*6dbdd20aSAndroid Build Coastguard Worker        m(
76*6dbdd20aSAndroid Build Coastguard Worker          'table',
77*6dbdd20aSAndroid Build Coastguard Worker          m(
78*6dbdd20aSAndroid Build Coastguard Worker            'tr',
79*6dbdd20aSAndroid Build Coastguard Worker            attrs.data.columns.map((col) =>
80*6dbdd20aSAndroid Build Coastguard Worker              this.formatColumnHeading(attrs.trace, col, attrs.aggregatorId),
81*6dbdd20aSAndroid Build Coastguard Worker            ),
82*6dbdd20aSAndroid Build Coastguard Worker          ),
83*6dbdd20aSAndroid Build Coastguard Worker          m(
84*6dbdd20aSAndroid Build Coastguard Worker            'tr.sum',
85*6dbdd20aSAndroid Build Coastguard Worker            attrs.data.columnSums.map((sum) => {
86*6dbdd20aSAndroid Build Coastguard Worker              const sumClass = sum === '' ? 'td' : 'td.sum-data';
87*6dbdd20aSAndroid Build Coastguard Worker              return m(sumClass, sum);
88*6dbdd20aSAndroid Build Coastguard Worker            }),
89*6dbdd20aSAndroid Build Coastguard Worker          ),
90*6dbdd20aSAndroid Build Coastguard Worker        ),
91*6dbdd20aSAndroid Build Coastguard Worker      ),
92*6dbdd20aSAndroid Build Coastguard Worker      m('.details-table.aggregation', m('table', this.getRows(attrs.data))),
93*6dbdd20aSAndroid Build Coastguard Worker    );
94*6dbdd20aSAndroid Build Coastguard Worker  }
95*6dbdd20aSAndroid Build Coastguard Worker
96*6dbdd20aSAndroid Build Coastguard Worker  formatColumnHeading(trace: TraceImpl, col: Column, aggregatorId: string) {
97*6dbdd20aSAndroid Build Coastguard Worker    const pref = trace.selection.aggregation.getSortingPrefs(aggregatorId);
98*6dbdd20aSAndroid Build Coastguard Worker    let sortIcon = '';
99*6dbdd20aSAndroid Build Coastguard Worker    if (pref && pref.column === col.columnId) {
100*6dbdd20aSAndroid Build Coastguard Worker      sortIcon =
101*6dbdd20aSAndroid Build Coastguard Worker        pref.direction === 'DESC' ? 'arrow_drop_down' : 'arrow_drop_up';
102*6dbdd20aSAndroid Build Coastguard Worker    }
103*6dbdd20aSAndroid Build Coastguard Worker    return m(
104*6dbdd20aSAndroid Build Coastguard Worker      'th',
105*6dbdd20aSAndroid Build Coastguard Worker      {
106*6dbdd20aSAndroid Build Coastguard Worker        onclick: () => {
107*6dbdd20aSAndroid Build Coastguard Worker          trace.selection.aggregation.toggleSortingColumn(
108*6dbdd20aSAndroid Build Coastguard Worker            aggregatorId,
109*6dbdd20aSAndroid Build Coastguard Worker            col.columnId,
110*6dbdd20aSAndroid Build Coastguard Worker          );
111*6dbdd20aSAndroid Build Coastguard Worker        },
112*6dbdd20aSAndroid Build Coastguard Worker      },
113*6dbdd20aSAndroid Build Coastguard Worker      col.title,
114*6dbdd20aSAndroid Build Coastguard Worker      m('i.material-icons', sortIcon),
115*6dbdd20aSAndroid Build Coastguard Worker    );
116*6dbdd20aSAndroid Build Coastguard Worker  }
117*6dbdd20aSAndroid Build Coastguard Worker
118*6dbdd20aSAndroid Build Coastguard Worker  getRows(data: AggregateData) {
119*6dbdd20aSAndroid Build Coastguard Worker    if (data.columns.length === 0) return;
120*6dbdd20aSAndroid Build Coastguard Worker    const rows = [];
121*6dbdd20aSAndroid Build Coastguard Worker    for (let i = 0; i < data.columns[0].data.length; i++) {
122*6dbdd20aSAndroid Build Coastguard Worker      const row = [];
123*6dbdd20aSAndroid Build Coastguard Worker      for (let j = 0; j < data.columns.length; j++) {
124*6dbdd20aSAndroid Build Coastguard Worker        row.push(m('td', this.getFormattedData(data, i, j)));
125*6dbdd20aSAndroid Build Coastguard Worker      }
126*6dbdd20aSAndroid Build Coastguard Worker      rows.push(m('tr', row));
127*6dbdd20aSAndroid Build Coastguard Worker    }
128*6dbdd20aSAndroid Build Coastguard Worker    return rows;
129*6dbdd20aSAndroid Build Coastguard Worker  }
130*6dbdd20aSAndroid Build Coastguard Worker
131*6dbdd20aSAndroid Build Coastguard Worker  getFormattedData(data: AggregateData, rowIndex: number, columnIndex: number) {
132*6dbdd20aSAndroid Build Coastguard Worker    switch (data.columns[columnIndex].kind) {
133*6dbdd20aSAndroid Build Coastguard Worker      case 'STRING':
134*6dbdd20aSAndroid Build Coastguard Worker        return data.strings[data.columns[columnIndex].data[rowIndex]];
135*6dbdd20aSAndroid Build Coastguard Worker      case 'TIMESTAMP_NS':
136*6dbdd20aSAndroid Build Coastguard Worker        return `${data.columns[columnIndex].data[rowIndex] / 1000000}`;
137*6dbdd20aSAndroid Build Coastguard Worker      case 'STATE': {
138*6dbdd20aSAndroid Build Coastguard Worker        const concatState =
139*6dbdd20aSAndroid Build Coastguard Worker          data.strings[data.columns[columnIndex].data[rowIndex]];
140*6dbdd20aSAndroid Build Coastguard Worker        const split = concatState.split(',');
141*6dbdd20aSAndroid Build Coastguard Worker        const ioWait =
142*6dbdd20aSAndroid Build Coastguard Worker          split[1] === 'NULL' ? undefined : !!Number.parseInt(split[1], 10);
143*6dbdd20aSAndroid Build Coastguard Worker        return translateState(split[0], ioWait);
144*6dbdd20aSAndroid Build Coastguard Worker      }
145*6dbdd20aSAndroid Build Coastguard Worker      case 'NUMBER':
146*6dbdd20aSAndroid Build Coastguard Worker      default:
147*6dbdd20aSAndroid Build Coastguard Worker        return data.columns[columnIndex].data[rowIndex];
148*6dbdd20aSAndroid Build Coastguard Worker    }
149*6dbdd20aSAndroid Build Coastguard Worker  }
150*6dbdd20aSAndroid Build Coastguard Worker
151*6dbdd20aSAndroid Build Coastguard Worker  showTimeRange() {
152*6dbdd20aSAndroid Build Coastguard Worker    const selection = this.trace.selection.selection;
153*6dbdd20aSAndroid Build Coastguard Worker    if (selection.kind !== 'area') return undefined;
154*6dbdd20aSAndroid Build Coastguard Worker    const duration = selection.end - selection.start;
155*6dbdd20aSAndroid Build Coastguard Worker    return m(
156*6dbdd20aSAndroid Build Coastguard Worker      '.time-range',
157*6dbdd20aSAndroid Build Coastguard Worker      'Selected range: ',
158*6dbdd20aSAndroid Build Coastguard Worker      m(DurationWidget, {dur: duration}),
159*6dbdd20aSAndroid Build Coastguard Worker    );
160*6dbdd20aSAndroid Build Coastguard Worker  }
161*6dbdd20aSAndroid Build Coastguard Worker
162*6dbdd20aSAndroid Build Coastguard Worker  // Thread state aggregation panel only
163*6dbdd20aSAndroid Build Coastguard Worker  showStateSummary(data: ThreadStateExtra) {
164*6dbdd20aSAndroid Build Coastguard Worker    if (data === undefined) return undefined;
165*6dbdd20aSAndroid Build Coastguard Worker    const states = [];
166*6dbdd20aSAndroid Build Coastguard Worker    for (let i = 0; i < data.states.length; i++) {
167*6dbdd20aSAndroid Build Coastguard Worker      const colorScheme = colorForState(data.states[i]);
168*6dbdd20aSAndroid Build Coastguard Worker      const width = (data.values[i] / data.totalMs) * 100;
169*6dbdd20aSAndroid Build Coastguard Worker      states.push(
170*6dbdd20aSAndroid Build Coastguard Worker        m(
171*6dbdd20aSAndroid Build Coastguard Worker          '.state',
172*6dbdd20aSAndroid Build Coastguard Worker          {
173*6dbdd20aSAndroid Build Coastguard Worker            style: {
174*6dbdd20aSAndroid Build Coastguard Worker              background: colorScheme.base.cssString,
175*6dbdd20aSAndroid Build Coastguard Worker              color: colorScheme.textBase.cssString,
176*6dbdd20aSAndroid Build Coastguard Worker              width: `${width}%`,
177*6dbdd20aSAndroid Build Coastguard Worker            },
178*6dbdd20aSAndroid Build Coastguard Worker          },
179*6dbdd20aSAndroid Build Coastguard Worker          `${data.states[i]}: ${data.values[i]} ms`,
180*6dbdd20aSAndroid Build Coastguard Worker        ),
181*6dbdd20aSAndroid Build Coastguard Worker      );
182*6dbdd20aSAndroid Build Coastguard Worker    }
183*6dbdd20aSAndroid Build Coastguard Worker    return m('.states', states);
184*6dbdd20aSAndroid Build Coastguard Worker  }
185*6dbdd20aSAndroid Build Coastguard Worker}
186