1// Copyright (C) 2020 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 * as m from 'mithril'; 16 17import {Actions} from '../common/actions'; 18 19import {Flow, globals} from './globals'; 20import {BLANK_CHECKBOX, CHECKBOX} from './icons'; 21import {Panel, PanelSize} from './panel'; 22import {findUiTrackId} from './scroll_helper'; 23 24export const ALL_CATEGORIES = '_all_'; 25 26export function getFlowCategories(flow: Flow): string[] { 27 const categories: string[] = []; 28 // v1 flows have their own categories 29 if (flow.category) { 30 categories.push(...flow.category.split(',')); 31 return categories; 32 } 33 const beginCats = flow.begin.sliceCategory.split(','); 34 const endCats = flow.end.sliceCategory.split(','); 35 categories.push(...new Set([...beginCats, ...endCats])); 36 return categories; 37} 38 39export class FlowEventsPanel extends Panel { 40 view() { 41 const selection = globals.state.currentSelection; 42 if (!selection || selection.kind !== 'CHROME_SLICE') { 43 return; 44 } 45 46 const flowClickHandler = (sliceId: number, trackId: number) => { 47 const uiTrackId = findUiTrackId(trackId); 48 if (uiTrackId) { 49 globals.makeSelection( 50 Actions.selectChromeSlice( 51 {id: sliceId, trackId: uiTrackId, table: 'slice'}), 52 'bound_flows'); 53 } 54 }; 55 56 // Can happen only for flow events version 1 57 const haveCategories = 58 globals.connectedFlows.filter(flow => flow.category).length > 0; 59 60 const columns = [ 61 m('th', 'Direction'), 62 m('th', 'Connected Slice ID'), 63 m('th', 'Connected Slice Name') 64 ]; 65 66 if (haveCategories) { 67 columns.push(m('th', 'Flow Category')); 68 columns.push(m('th', 'Flow Name')); 69 } 70 71 const rows = [m('tr', columns)]; 72 73 // Fill the table with all the directly connected flow events 74 globals.connectedFlows.forEach(flow => { 75 if (selection.id !== flow.begin.sliceId && 76 selection.id !== flow.end.sliceId) { 77 return; 78 } 79 80 const outgoing = selection.id === flow.begin.sliceId; 81 const otherEnd = (outgoing ? flow.end : flow.begin); 82 83 const args = { 84 onclick: () => flowClickHandler(otherEnd.sliceId, otherEnd.trackId), 85 onmousemove: () => 86 globals.frontendLocalState.setHighlightedSliceId(otherEnd.sliceId), 87 onmouseleave: () => globals.frontendLocalState.setHighlightedSliceId(-1) 88 }; 89 90 const data = [ 91 m('td.flow-link', args, outgoing ? 'Outgoing' : 'Incoming'), 92 m('td.flow-link', args, otherEnd.sliceId.toString()), 93 m('td.flow-link', args, otherEnd.sliceName) 94 ]; 95 96 if (haveCategories) { 97 data.push(m('td.flow-info', flow.category || '-')); 98 data.push(m('td.flow-info', flow.name || '-')); 99 } 100 101 rows.push(m('tr', data)); 102 }); 103 104 return m('.details-panel', [ 105 m('.details-panel-heading', m('h2', `Flow events`)), 106 m('.flow-events-table', m('table.half-width', rows)) 107 ]); 108 } 109 110 renderCanvas(_ctx: CanvasRenderingContext2D, _size: PanelSize) {} 111} 112 113export class FlowEventsAreaSelectedPanel extends Panel { 114 view() { 115 const selection = globals.state.currentSelection; 116 if (!selection || selection.kind !== 'AREA') { 117 return; 118 } 119 120 const columns = [ 121 m('th', 'Flow Category'), 122 m('th', 'Number of flows'), 123 m('th', 124 'Show', 125 m('a.warning', 126 m('i.material-icons', 'warning'), 127 m('.tooltip', 128 'Showing a large number of flows may impact performance.'))) 129 ]; 130 131 const rows = [m('tr', columns)]; 132 133 const categoryToFlowsNum = new Map<string, number>(); 134 135 globals.selectedFlows.forEach(flow => { 136 const categories = getFlowCategories(flow); 137 categories.forEach(cat => { 138 if (!categoryToFlowsNum.has(cat)) { 139 categoryToFlowsNum.set(cat, 0); 140 } 141 categoryToFlowsNum.set(cat, categoryToFlowsNum.get(cat)! + 1); 142 }); 143 }); 144 145 const allWasChecked = globals.visibleFlowCategories.get(ALL_CATEGORIES); 146 rows.push(m('tr.sum', [ 147 m('td.sum-data', 'All'), 148 m('td.sum-data', globals.selectedFlows.length), 149 m('td.sum-data', 150 m('i.material-icons', 151 { 152 onclick: () => { 153 if (allWasChecked) { 154 globals.visibleFlowCategories.clear(); 155 } else { 156 categoryToFlowsNum.forEach((_, cat) => { 157 globals.visibleFlowCategories.set(cat, true); 158 }); 159 } 160 globals.visibleFlowCategories.set(ALL_CATEGORIES, !allWasChecked); 161 globals.rafScheduler.scheduleFullRedraw(); 162 }, 163 }, 164 allWasChecked ? CHECKBOX : BLANK_CHECKBOX)) 165 ])); 166 167 categoryToFlowsNum.forEach((num, cat) => { 168 const wasChecked = globals.visibleFlowCategories.get(cat) || 169 globals.visibleFlowCategories.get(ALL_CATEGORIES); 170 const data = [ 171 m('td.flow-info', cat), 172 m('td.flow-info', num), 173 m('td.flow-info', 174 m('i.material-icons', 175 { 176 onclick: () => { 177 if (wasChecked) { 178 globals.visibleFlowCategories.set(ALL_CATEGORIES, false); 179 } 180 globals.visibleFlowCategories.set(cat, !wasChecked); 181 globals.rafScheduler.scheduleFullRedraw(); 182 }, 183 }, 184 wasChecked ? CHECKBOX : BLANK_CHECKBOX)) 185 ]; 186 rows.push(m('tr', data)); 187 }); 188 189 return m('.details-panel', [ 190 m('.details-panel-heading', m('h2', `Selected flow events`)), 191 m('.flow-events-table', m('table.half-width', rows)), 192 ]); 193 } 194 195 renderCanvas(_ctx: CanvasRenderingContext2D, _size: PanelSize) {} 196} 197