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