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'; 18import {timeToCode} from '../common/time'; 19 20import {Flow, globals} from './globals'; 21import {BLANK_CHECKBOX, CHECKBOX} from './icons'; 22import {Panel, PanelSize} from './panel'; 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 = globals.state.uiTrackIdByTraceTrackId[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', 'Duration'), 63 m('th', 'Connected Slice ID'), 64 m('th', 'Connected Slice Name'), 65 m('th', 'Thread Out'), 66 m('th', 'Thread In'), 67 m('th', 'Process Out'), 68 m('th', 'Process In') 69 ]; 70 71 if (haveCategories) { 72 columns.push(m('th', 'Flow Category')); 73 columns.push(m('th', 'Flow Name')); 74 } 75 76 const rows = [m('tr', columns)]; 77 78 // Fill the table with all the directly connected flow events 79 globals.connectedFlows.forEach(flow => { 80 if (selection.id !== flow.begin.sliceId && 81 selection.id !== flow.end.sliceId) { 82 return; 83 } 84 85 const outgoing = selection.id === flow.begin.sliceId; 86 const otherEnd = (outgoing ? flow.end : flow.begin); 87 88 const args = { 89 onclick: () => flowClickHandler(otherEnd.sliceId, otherEnd.trackId), 90 onmousemove: () => globals.dispatch( 91 Actions.setHighlightedSliceId({sliceId: otherEnd.sliceId})), 92 onmouseleave: () => 93 globals.dispatch(Actions.setHighlightedSliceId({sliceId: -1})), 94 }; 95 96 const data = [ 97 m('td.flow-link', args, outgoing ? 'Outgoing' : 'Incoming'), 98 m('td.flow-link', args, timeToCode(flow.dur)), 99 m('td.flow-link', args, otherEnd.sliceId.toString()), 100 m('td.flow-link', args, otherEnd.sliceName), 101 m('td.flow-link', args, flow.begin.threadName), 102 m('td.flow-link', args, flow.end.threadName), 103 m('td.flow-link', args, flow.begin.processName), 104 m('td.flow-link', args, flow.end.processName) 105 ]; 106 107 if (haveCategories) { 108 data.push(m('td.flow-info', flow.category || '-')); 109 data.push(m('td.flow-info', flow.name || '-')); 110 } 111 112 rows.push(m('tr', data)); 113 }); 114 115 return m('.details-panel', [ 116 m('.details-panel-heading', m('h2', `Flow events`)), 117 m('.flow-events-table', m('table', rows)) 118 ]); 119 } 120 121 renderCanvas(_ctx: CanvasRenderingContext2D, _size: PanelSize) {} 122} 123 124export class FlowEventsAreaSelectedPanel extends Panel { 125 view() { 126 const selection = globals.state.currentSelection; 127 if (!selection || selection.kind !== 'AREA') { 128 return; 129 } 130 131 const columns = [ 132 m('th', 'Flow Category'), 133 m('th', 'Number of flows'), 134 m('th', 135 'Show', 136 m('a.warning', 137 m('i.material-icons', 'warning'), 138 m('.tooltip', 139 'Showing a large number of flows may impact performance.'))) 140 ]; 141 142 const rows = [m('tr', columns)]; 143 144 const categoryToFlowsNum = new Map<string, number>(); 145 146 globals.selectedFlows.forEach(flow => { 147 const categories = getFlowCategories(flow); 148 categories.forEach(cat => { 149 if (!categoryToFlowsNum.has(cat)) { 150 categoryToFlowsNum.set(cat, 0); 151 } 152 categoryToFlowsNum.set(cat, categoryToFlowsNum.get(cat)! + 1); 153 }); 154 }); 155 156 const allWasChecked = globals.visibleFlowCategories.get(ALL_CATEGORIES); 157 rows.push(m('tr.sum', [ 158 m('td.sum-data', 'All'), 159 m('td.sum-data', globals.selectedFlows.length), 160 m('td.sum-data', 161 m('i.material-icons', 162 { 163 onclick: () => { 164 if (allWasChecked) { 165 globals.visibleFlowCategories.clear(); 166 } else { 167 categoryToFlowsNum.forEach((_, cat) => { 168 globals.visibleFlowCategories.set(cat, true); 169 }); 170 } 171 globals.visibleFlowCategories.set(ALL_CATEGORIES, !allWasChecked); 172 globals.rafScheduler.scheduleFullRedraw(); 173 }, 174 }, 175 allWasChecked ? CHECKBOX : BLANK_CHECKBOX)) 176 ])); 177 178 categoryToFlowsNum.forEach((num, cat) => { 179 const wasChecked = globals.visibleFlowCategories.get(cat) || 180 globals.visibleFlowCategories.get(ALL_CATEGORIES); 181 const data = [ 182 m('td.flow-info', cat), 183 m('td.flow-info', num), 184 m('td.flow-info', 185 m('i.material-icons', 186 { 187 onclick: () => { 188 if (wasChecked) { 189 globals.visibleFlowCategories.set(ALL_CATEGORIES, false); 190 } 191 globals.visibleFlowCategories.set(cat, !wasChecked); 192 globals.rafScheduler.scheduleFullRedraw(); 193 }, 194 }, 195 wasChecked ? CHECKBOX : BLANK_CHECKBOX)) 196 ]; 197 rows.push(m('tr', data)); 198 }); 199 200 return m('.details-panel', [ 201 m('.details-panel-heading', m('h2', `Selected flow events`)), 202 m('.flow-events-table', m('table.half-width', rows)), 203 ]); 204 } 205 206 renderCanvas(_ctx: CanvasRenderingContext2D, _size: PanelSize) {} 207} 208