• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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';
16
17import {Actions} from '../common/actions';
18import {
19  AggregateData,
20  Column,
21  ThreadStateExtra,
22  isEmptyData,
23} from '../common/aggregation_data';
24import {colorForState} from '../core/colorizer';
25import {translateState} from '../common/thread_state';
26
27import {globals} from './globals';
28import {DurationWidget} from './widgets/duration';
29import {EmptyState} from '../widgets/empty_state';
30import {Anchor} from '../widgets/anchor';
31import {Icons} from '../base/semantic_icons';
32
33export interface AggregationPanelAttrs {
34  data?: AggregateData;
35  kind: string;
36}
37
38export class AggregationPanel
39  implements m.ClassComponent<AggregationPanelAttrs>
40{
41  view({attrs}: m.CVnode<AggregationPanelAttrs>) {
42    if (!attrs.data || isEmptyData(attrs.data)) {
43      return m(
44        EmptyState,
45        {
46          className: 'pf-noselection',
47          title: 'No relevant tracks in selection',
48        },
49        m(
50          Anchor,
51          {
52            icon: Icons.ChangeTab,
53            onclick: () => {
54              globals.dispatch(Actions.showTab({uri: 'current_selection'}));
55            },
56          },
57          'Go to current selection tab',
58        ),
59      );
60    }
61
62    return m(
63      '.details-panel',
64      m(
65        '.details-panel-heading.aggregation',
66        attrs.data.extra !== undefined &&
67          attrs.data.extra.kind === 'THREAD_STATE'
68          ? this.showStateSummary(attrs.data.extra)
69          : null,
70        this.showTimeRange(),
71        m(
72          'table',
73          m(
74            'tr',
75            attrs.data.columns.map((col) =>
76              this.formatColumnHeading(col, attrs.kind),
77            ),
78          ),
79          m(
80            'tr.sum',
81            attrs.data.columnSums.map((sum) => {
82              const sumClass = sum === '' ? 'td' : 'td.sum-data';
83              return m(sumClass, sum);
84            }),
85          ),
86        ),
87      ),
88      m('.details-table.aggregation', m('table', this.getRows(attrs.data))),
89    );
90  }
91
92  formatColumnHeading(col: Column, id: string) {
93    const pref = globals.state.aggregatePreferences[id];
94    let sortIcon = '';
95    // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
96    if (pref && pref.sorting && pref.sorting.column === col.columnId) {
97      sortIcon =
98        pref.sorting.direction === 'DESC' ? 'arrow_drop_down' : 'arrow_drop_up';
99    }
100    return m(
101      'th',
102      {
103        onclick: () => {
104          globals.dispatch(
105            Actions.updateAggregateSorting({id, column: col.columnId}),
106          );
107        },
108      },
109      col.title,
110      m('i.material-icons', sortIcon),
111    );
112  }
113
114  getRows(data: AggregateData) {
115    if (data.columns.length === 0) return;
116    const rows = [];
117    for (let i = 0; i < data.columns[0].data.length; i++) {
118      const row = [];
119      for (let j = 0; j < data.columns.length; j++) {
120        row.push(m('td', this.getFormattedData(data, i, j)));
121      }
122      rows.push(m('tr', row));
123    }
124    return rows;
125  }
126
127  getFormattedData(data: AggregateData, rowIndex: number, columnIndex: number) {
128    switch (data.columns[columnIndex].kind) {
129      case 'STRING':
130        return data.strings[data.columns[columnIndex].data[rowIndex]];
131      case 'TIMESTAMP_NS':
132        return `${data.columns[columnIndex].data[rowIndex] / 1000000}`;
133      case 'STATE': {
134        const concatState =
135          data.strings[data.columns[columnIndex].data[rowIndex]];
136        const split = concatState.split(',');
137        const ioWait =
138          split[1] === 'NULL' ? undefined : !!Number.parseInt(split[1], 10);
139        return translateState(split[0], ioWait);
140      }
141      case 'NUMBER':
142      default:
143        return data.columns[columnIndex].data[rowIndex];
144    }
145  }
146
147  showTimeRange() {
148    const selection = globals.state.selection;
149    if (selection.kind !== 'area') return undefined;
150    const duration = selection.end - selection.start;
151    return m(
152      '.time-range',
153      'Selected range: ',
154      m(DurationWidget, {dur: duration}),
155    );
156  }
157
158  // Thread state aggregation panel only
159  showStateSummary(data: ThreadStateExtra) {
160    if (data === undefined) return undefined;
161    const states = [];
162    for (let i = 0; i < data.states.length; i++) {
163      const colorScheme = colorForState(data.states[i]);
164      const width = (data.values[i] / data.totalMs) * 100;
165      states.push(
166        m(
167          '.state',
168          {
169            style: {
170              background: colorScheme.base.cssString,
171              color: colorScheme.textBase.cssString,
172              width: `${width}%`,
173            },
174          },
175          `${data.states[i]}: ${data.values[i]} ms`,
176        ),
177      );
178    }
179    return m('.states', states);
180  }
181}
182