• 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';
16import {
17  AggregateData,
18  Column,
19  Sorting,
20  ThreadStateExtra,
21} from '../public/aggregation';
22import {colorForState} from './colorizer';
23import {DurationWidget} from './widgets/duration';
24import {translateState} from './sql_utils/thread_state';
25import {Trace} from '../public/trace';
26
27export interface AggregationPanelAttrs {
28  readonly trace: Trace;
29  readonly data: AggregateData;
30  readonly model: AggState;
31}
32
33export interface AggState {
34  getSortingPrefs(): Sorting | undefined;
35  toggleSortingColumn(column: string): void;
36}
37
38export class AggregationPanel
39  implements m.ClassComponent<AggregationPanelAttrs>
40{
41  private trace: Trace;
42
43  constructor({attrs}: m.CVnode<AggregationPanelAttrs>) {
44    this.trace = attrs.trace;
45  }
46
47  view({attrs}: m.CVnode<AggregationPanelAttrs>) {
48    return m(
49      '.details-panel',
50      m(
51        '.details-panel-heading.aggregation',
52        attrs.data.extra !== undefined &&
53          attrs.data.extra.kind === 'THREAD_STATE'
54          ? this.showStateSummary(attrs.data.extra)
55          : null,
56        this.showTimeRange(),
57        m(
58          'table',
59          m(
60            'tr',
61            attrs.data.columns.map((col) =>
62              this.formatColumnHeading(col, attrs.model),
63            ),
64          ),
65          m(
66            'tr.sum',
67            attrs.data.columnSums.map((sum) => {
68              const sumClass = sum === '' ? 'td' : 'td.sum-data';
69              return m(sumClass, sum);
70            }),
71          ),
72        ),
73      ),
74      m('.details-table.aggregation', m('table', this.getRows(attrs.data))),
75    );
76  }
77
78  formatColumnHeading(col: Column, model: AggState) {
79    const pref = model.getSortingPrefs();
80    let sortIcon = '';
81    if (pref && pref.column === col.columnId) {
82      sortIcon =
83        pref.direction === 'DESC' ? 'arrow_drop_down' : 'arrow_drop_up';
84    }
85    return m(
86      'th',
87      {
88        onclick: () => {
89          model.toggleSortingColumn(col.columnId);
90        },
91      },
92      col.title,
93      m('i.material-icons', sortIcon),
94    );
95  }
96
97  getRows(data: AggregateData) {
98    if (data.columns.length === 0) return;
99    const rows = [];
100    for (let i = 0; i < data.columns[0].data.length; i++) {
101      const row = [];
102      for (let j = 0; j < data.columns.length; j++) {
103        row.push(m('td', this.getFormattedData(data, i, j)));
104      }
105      rows.push(m('tr', row));
106    }
107    return rows;
108  }
109
110  getFormattedData(data: AggregateData, rowIndex: number, columnIndex: number) {
111    switch (data.columns[columnIndex].kind) {
112      case 'STRING':
113        return data.strings[data.columns[columnIndex].data[rowIndex]];
114      case 'TIMESTAMP_NS':
115        return `${data.columns[columnIndex].data[rowIndex] / 1000000}`;
116      case 'STATE': {
117        const concatState =
118          data.strings[data.columns[columnIndex].data[rowIndex]];
119        const split = concatState.split(',');
120        const ioWait =
121          split[1] === 'NULL' ? undefined : !!Number.parseInt(split[1], 10);
122        return translateState(split[0], ioWait);
123      }
124      case 'NUMBER':
125      default:
126        return data.columns[columnIndex].data[rowIndex];
127    }
128  }
129
130  showTimeRange() {
131    const selection = this.trace.selection.selection;
132    if (selection.kind !== 'area') return undefined;
133    const duration = selection.end - selection.start;
134    return m(
135      '.time-range',
136      'Selected range: ',
137      m(DurationWidget, {dur: duration}),
138    );
139  }
140
141  // Thread state aggregation panel only
142  showStateSummary(data: ThreadStateExtra) {
143    if (data === undefined) return undefined;
144    const states = [];
145    for (let i = 0; i < data.states.length; i++) {
146      const colorScheme = colorForState(data.states[i]);
147      const width = (data.values[i] / data.totalMs) * 100;
148      states.push(
149        m(
150          '.state',
151          {
152            style: {
153              background: colorScheme.base.cssString,
154              color: colorScheme.textBase.cssString,
155              width: `${width}%`,
156            },
157          },
158          `${data.states[i]}: ${data.values[i]} ms`,
159        ),
160      );
161    }
162    return m('.states', states);
163  }
164}
165