• 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 m from 'mithril';
16
17import {Icons} from '../base/semantic_icons';
18import {Actions} from '../common/actions';
19import {getLegacySelection} from '../common/state';
20import {raf} from '../core/raf_scheduler';
21
22import {Flow, globals} from './globals';
23import {DurationWidget} from './widgets/duration';
24import {EmptyState} from '../widgets/empty_state';
25
26export const ALL_CATEGORIES = '_all_';
27
28export function getFlowCategories(flow: Flow): string[] {
29  const categories: string[] = [];
30  // v1 flows have their own categories
31  if (flow.category) {
32    categories.push(...flow.category.split(','));
33    return categories;
34  }
35  const beginCats = flow.begin.sliceCategory.split(',');
36  const endCats = flow.end.sliceCategory.split(',');
37  categories.push(...new Set([...beginCats, ...endCats]));
38  return categories;
39}
40
41export class FlowEventsPanel implements m.ClassComponent {
42  view() {
43    const selection = getLegacySelection(globals.state);
44    if (!selection) {
45      return m(
46        EmptyState,
47        {
48          className: 'pf-noselection',
49          title: 'Nothing selected',
50        },
51        'Flow data will appear here',
52      );
53    }
54
55    if (selection.kind !== 'SLICE') {
56      return m(
57        EmptyState,
58        {
59          className: 'pf-noselection',
60          title: 'No flow data',
61          icon: 'warning',
62        },
63        `Flows are not applicable to the selection kind: '${selection.kind}'`,
64      );
65    }
66
67    const flowClickHandler = (sliceId: number, trackId: number) => {
68      const trackKey = globals.trackManager.trackKeyByTrackId.get(trackId);
69      if (trackKey) {
70        globals.setLegacySelection(
71          {
72            kind: 'SLICE',
73            id: sliceId,
74            trackKey,
75            table: 'slice',
76          },
77          {
78            clearSearch: true,
79            pendingScrollId: undefined,
80            switchToCurrentSelectionTab: false,
81          },
82        );
83      }
84    };
85
86    // Can happen only for flow events version 1
87    const haveCategories =
88      globals.connectedFlows.filter((flow) => flow.category).length > 0;
89
90    const columns = [
91      m('th', 'Direction'),
92      m('th', 'Duration'),
93      m('th', 'Connected Slice ID'),
94      m('th', 'Connected Slice Name'),
95      m('th', 'Thread Out'),
96      m('th', 'Thread In'),
97      m('th', 'Process Out'),
98      m('th', 'Process In'),
99    ];
100
101    if (haveCategories) {
102      columns.push(m('th', 'Flow Category'));
103      columns.push(m('th', 'Flow Name'));
104    }
105
106    const rows = [m('tr', columns)];
107
108    // Fill the table with all the directly connected flow events
109    globals.connectedFlows.forEach((flow) => {
110      if (
111        selection.id !== flow.begin.sliceId &&
112        selection.id !== flow.end.sliceId
113      ) {
114        return;
115      }
116
117      const outgoing = selection.id === flow.begin.sliceId;
118      const otherEnd = outgoing ? flow.end : flow.begin;
119
120      const args = {
121        onclick: () => flowClickHandler(otherEnd.sliceId, otherEnd.trackId),
122        onmousemove: () =>
123          globals.dispatch(
124            Actions.setHighlightedSliceId({sliceId: otherEnd.sliceId}),
125          ),
126        onmouseleave: () =>
127          globals.dispatch(Actions.setHighlightedSliceId({sliceId: -1})),
128      };
129
130      const data = [
131        m('td.flow-link', args, outgoing ? 'Outgoing' : 'Incoming'),
132        m('td.flow-link', args, m(DurationWidget, {dur: flow.dur})),
133        m('td.flow-link', args, otherEnd.sliceId.toString()),
134        m('td.flow-link', args, otherEnd.sliceName),
135        m('td.flow-link', args, flow.begin.threadName),
136        m('td.flow-link', args, flow.end.threadName),
137        m('td.flow-link', args, flow.begin.processName),
138        m('td.flow-link', args, flow.end.processName),
139      ];
140
141      if (haveCategories) {
142        data.push(m('td.flow-info', flow.category || '-'));
143        data.push(m('td.flow-info', flow.name || '-'));
144      }
145
146      rows.push(m('tr', data));
147    });
148
149    return m('.details-panel', [
150      m('.details-panel-heading', m('h2', `Flow events`)),
151      m('.flow-events-table', m('table', rows)),
152    ]);
153  }
154}
155
156export class FlowEventsAreaSelectedPanel implements m.ClassComponent {
157  view() {
158    const selection = globals.state.selection;
159    if (selection.kind !== 'area') {
160      return;
161    }
162
163    const columns = [
164      m('th', 'Flow Category'),
165      m('th', 'Number of flows'),
166      m(
167        'th',
168        'Show',
169        m(
170          'a.warning',
171          m('i.material-icons', 'warning'),
172          m(
173            '.tooltip',
174            'Showing a large number of flows may impact performance.',
175          ),
176        ),
177      ),
178    ];
179
180    const rows = [m('tr', columns)];
181
182    const categoryToFlowsNum = new Map<string, number>();
183
184    globals.selectedFlows.forEach((flow) => {
185      const categories = getFlowCategories(flow);
186      categories.forEach((cat) => {
187        if (!categoryToFlowsNum.has(cat)) {
188          categoryToFlowsNum.set(cat, 0);
189        }
190        categoryToFlowsNum.set(cat, categoryToFlowsNum.get(cat)! + 1);
191      });
192    });
193
194    const allWasChecked = globals.visibleFlowCategories.get(ALL_CATEGORIES);
195    rows.push(
196      m('tr.sum', [
197        m('td.sum-data', 'All'),
198        m('td.sum-data', globals.selectedFlows.length),
199        m(
200          'td.sum-data',
201          m(
202            'i.material-icons',
203            {
204              onclick: () => {
205                if (allWasChecked) {
206                  globals.visibleFlowCategories.clear();
207                } else {
208                  categoryToFlowsNum.forEach((_, cat) => {
209                    globals.visibleFlowCategories.set(cat, true);
210                  });
211                }
212                globals.visibleFlowCategories.set(
213                  ALL_CATEGORIES,
214                  !allWasChecked,
215                );
216                raf.scheduleFullRedraw();
217              },
218            },
219            allWasChecked ? Icons.Checkbox : Icons.BlankCheckbox,
220          ),
221        ),
222      ]),
223    );
224
225    categoryToFlowsNum.forEach((num, cat) => {
226      const wasChecked =
227        globals.visibleFlowCategories.get(cat) ||
228        globals.visibleFlowCategories.get(ALL_CATEGORIES);
229      const data = [
230        m('td.flow-info', cat),
231        m('td.flow-info', num),
232        m(
233          'td.flow-info',
234          m(
235            'i.material-icons',
236            {
237              onclick: () => {
238                if (wasChecked) {
239                  globals.visibleFlowCategories.set(ALL_CATEGORIES, false);
240                }
241                globals.visibleFlowCategories.set(cat, !wasChecked);
242                raf.scheduleFullRedraw();
243              },
244            },
245            wasChecked ? Icons.Checkbox : Icons.BlankCheckbox,
246          ),
247        ),
248      ];
249      rows.push(m('tr', data));
250    });
251
252    return m('.details-panel', [
253      m('.details-panel-heading', m('h2', `Selected flow events`)),
254      m('.flow-events-table', m('table', rows)),
255    ]);
256  }
257}
258