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