// Copyright (C) 2019 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use size file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import m from 'mithril'; import {Actions} from '../common/actions'; import { AggregateData, Column, ThreadStateExtra, } from '../common/aggregation_data'; import {colorForState, textColorForState} from '../common/colorizer'; import {translateState} from '../common/thread_state'; import {tpTimeToMillis} from '../common/time'; import {globals} from './globals'; import {Panel} from './panel'; export interface AggregationPanelAttrs { data: AggregateData; kind: string; } export class AggregationPanel extends Panel { view({attrs}: m.CVnode) { return m( '.details-panel', m('.details-panel-heading.aggregation', attrs.data.extra !== undefined && attrs.data.extra.kind === 'THREAD_STATE' ? this.showStateSummary(attrs.data.extra) : null, this.showTimeRange(), m('table', m('tr', attrs.data.columns.map( (col) => this.formatColumnHeading(col, attrs.kind))), m('tr.sum', attrs.data.columnSums.map((sum) => { const sumClass = sum === '' ? 'td' : 'td.sum-data'; return m(sumClass, sum); })))), m( '.details-table.aggregation', m('table', this.getRows(attrs.data)), )); } formatColumnHeading(col: Column, id: string) { const pref = globals.state.aggregatePreferences[id]; let sortIcon = ''; if (pref && pref.sorting && pref.sorting.column === col.columnId) { sortIcon = pref.sorting.direction === 'DESC' ? 'arrow_drop_down' : 'arrow_drop_up'; } return m( 'th', { onclick: () => { globals.dispatch( Actions.updateAggregateSorting({id, column: col.columnId})); }, }, col.title, m('i.material-icons', sortIcon)); } getRows(data: AggregateData) { if (data.columns.length === 0) return; const rows = []; for (let i = 0; i < data.columns[0].data.length; i++) { const row = []; for (let j = 0; j < data.columns.length; j++) { row.push(m('td', this.getFormattedData(data, i, j))); } rows.push(m('tr', row)); } return rows; } getFormattedData(data: AggregateData, rowIndex: number, columnIndex: number) { switch (data.columns[columnIndex].kind) { case 'STRING': return data.strings[data.columns[columnIndex].data[rowIndex]]; case 'TIMESTAMP_NS': return `${data.columns[columnIndex].data[rowIndex] / 1000000}`; case 'STATE': { const concatState = data.strings[data.columns[columnIndex].data[rowIndex]]; const split = concatState.split(','); const ioWait = split[1] === 'NULL' ? undefined : !!Number.parseInt(split[1], 10); return translateState(split[0], ioWait); } case 'NUMBER': default: return data.columns[columnIndex].data[rowIndex]; } } showTimeRange() { const selection = globals.state.currentSelection; if (selection === null || selection.kind !== 'AREA') return undefined; const selectedArea = globals.state.areas[selection.areaId]; const rangeDurationMs = tpTimeToMillis(selectedArea.end - selectedArea.start); return m('.time-range', `Selected range: ${rangeDurationMs.toFixed(6)} ms`); } // Thread state aggregation panel only showStateSummary(data: ThreadStateExtra) { if (data === undefined) return undefined; const states = []; for (let i = 0; i < data.states.length; i++) { const color = colorForState(data.states[i]); const textColor = textColorForState(data.states[i]); const width = data.values[i] / data.totalMs * 100; states.push( m('.state', { style: { background: `hsl(${color.h},${color.s}%,${color.l}%)`, color: `${textColor}`, width: `${width}%`, }, }, `${data.states[i]}: ${data.values[i]} ms`)); } return m('.states', states); } renderCanvas() {} }