• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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