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 {Engine} from '../common/engine';
16import {slowlyCountRows} from '../common/query_iterator';
17import {Area} from '../common/state';
18import {fromNs, toNs} from '../common/time';
19import {Flow} from '../frontend/globals';
20import {
21  ACTUAL_FRAMES_SLICE_TRACK_KIND,
22  Config as ActualConfig
23} from '../tracks/actual_frames/common';
24import {
25  Config as SliceConfig,
26  SLICE_TRACK_KIND
27} from '../tracks/chrome_slices/common';
28
29import {Controller} from './controller';
30import {globals} from './globals';
31
32export interface FlowEventsControllerArgs {
33  engine: Engine;
34}
35
36export class FlowEventsController extends Controller<'main'> {
37  private lastSelectedSliceId?: number;
38  private lastSelectedArea?: Area;
39  private lastSelectedKind: 'CHROME_SLICE'|'AREA'|'NONE' = 'NONE';
40
41  constructor(private args: FlowEventsControllerArgs) {
42    super('main');
43  }
44
45  queryFlowEvents(query: string, callback: (flows: Flow[]) => void) {
46    this.args.engine.query(query).then(res => {
47      const flows: Flow[] = [];
48      for (let i = 0; i < slowlyCountRows(res); i++) {
49        const beginSliceId = res.columns[0].longValues![i];
50        const beginTrackId = res.columns[1].longValues![i];
51        const beginSliceName = res.columns[2].stringValues![i];
52        const beginSliceCategory = res.columns[3].stringValues![i];
53        const beginSliceStartTs = fromNs(res.columns[4].longValues![i]);
54        const beginSliceEndTs = fromNs(res.columns[5].longValues![i]);
55        const beginDepth = res.columns[6].longValues![i];
56
57        const endSliceId = res.columns[7].longValues![i];
58        const endTrackId = res.columns[8].longValues![i];
59        const endSliceName = res.columns[9].stringValues![i];
60        const endSliceCategory = res.columns[10].stringValues![i];
61        const endSliceStartTs = fromNs(res.columns[11].longValues![i]);
62        const endSliceEndTs = fromNs(res.columns[12].longValues![i]);
63        const endDepth = res.columns[13].longValues![i];
64
65        // Category and name present only in version 1 flow events
66        // It is most likelly NULL for all other versions
67        const category = res.columns[14].isNulls![i] ?
68            undefined :
69            res.columns[14].stringValues![i];
70        const name = res.columns[15].isNulls![i] ?
71            undefined :
72            res.columns[15].stringValues![i];
73        const id = res.columns[16].longValues![i];
74
75        flows.push({
76          id,
77          begin: {
78            trackId: beginTrackId,
79            sliceId: beginSliceId,
80            sliceName: beginSliceName,
81            sliceCategory: beginSliceCategory,
82            sliceStartTs: beginSliceStartTs,
83            sliceEndTs: beginSliceEndTs,
84            depth: beginDepth
85          },
86          end: {
87            trackId: endTrackId,
88            sliceId: endSliceId,
89            sliceName: endSliceName,
90            sliceCategory: endSliceCategory,
91            sliceStartTs: endSliceStartTs,
92            sliceEndTs: endSliceEndTs,
93            depth: endDepth
94          },
95          category,
96          name
97        });
98      }
99      callback(flows);
100    });
101  }
102
103  sliceSelected(sliceId: number) {
104    if (this.lastSelectedKind === 'CHROME_SLICE' &&
105        this.lastSelectedSliceId === sliceId) {
106      return;
107    }
108    this.lastSelectedSliceId = sliceId;
109    this.lastSelectedKind = 'CHROME_SLICE';
110
111    const query = `
112    select
113      f.slice_out, t1.track_id, t1.name,
114      t1.category, t1.ts, (t1.ts+t1.dur), t1.depth,
115      f.slice_in, t2.track_id, t2.name,
116      t2.category, t2.ts, (t2.ts+t2.dur), t2.depth,
117      extract_arg(f.arg_set_id, 'cat'),
118      extract_arg(f.arg_set_id, 'name'),
119      f.id
120    from directly_connected_flow(${sliceId}) f
121    join slice t1 on f.slice_out = t1.slice_id
122    join slice t2 on f.slice_in = t2.slice_id
123    `;
124    this.queryFlowEvents(
125        query, (flows: Flow[]) => globals.publish('ConnectedFlows', flows));
126  }
127
128  areaSelected(areaId: string) {
129    const area = globals.state.areas[areaId];
130    if (this.lastSelectedKind === 'AREA' && this.lastSelectedArea &&
131        this.lastSelectedArea.tracks.join(',') === area.tracks.join(',') &&
132        this.lastSelectedArea.endSec === area.endSec &&
133        this.lastSelectedArea.startSec === area.startSec) {
134      return;
135    }
136
137    this.lastSelectedArea = area;
138    this.lastSelectedKind = 'AREA';
139
140    const trackIds: number[] = [];
141
142    for (const uiTrackId of area.tracks) {
143      const track = globals.state.tracks[uiTrackId];
144      if (track === undefined) {
145        continue;
146      }
147      if (track.kind === SLICE_TRACK_KIND) {
148        trackIds.push((track.config as SliceConfig).trackId);
149      } else if (track.kind === ACTUAL_FRAMES_SLICE_TRACK_KIND) {
150        const actualConfig = track.config as ActualConfig;
151        for (const trackId of actualConfig.trackIds) {
152          trackIds.push(trackId);
153        }
154      }
155    }
156
157    const tracks = `(${trackIds.join(',')})`;
158
159    const startNs = toNs(area.startSec);
160    const endNs = toNs(area.endSec);
161
162    const query = `
163    select
164      f.slice_out, t1.track_id, t1.name,
165      t1.category, t1.ts, (t1.ts+t1.dur), t1.depth,
166      f.slice_in, t2.track_id, t2.name,
167      t2.category, t2.ts, (t2.ts+t2.dur), t2.depth,
168      extract_arg(f.arg_set_id, 'cat'),
169      extract_arg(f.arg_set_id, 'name'),
170      f.id
171    from flow f
172    join slice t1 on f.slice_out = t1.slice_id
173    join slice t2 on f.slice_in = t2.slice_id
174    where
175      (t1.track_id in ${tracks}
176        and (t1.ts+t1.dur <= ${endNs} and t1.ts+t1.dur >= ${startNs}))
177      or
178      (t2.track_id in ${tracks}
179        and (t2.ts <= ${endNs} and t2.ts >= ${startNs}))
180    `;
181    this.queryFlowEvents(
182        query, (flows: Flow[]) => globals.publish('SelectedFlows', flows));
183  }
184
185  refreshVisibleFlows() {
186    const selection = globals.state.currentSelection;
187    if (!selection) {
188      this.lastSelectedKind = 'NONE';
189      globals.publish('ConnectedFlows', []);
190      globals.publish('SelectedFlows', []);
191      return;
192    }
193
194    if (selection && selection.kind === 'CHROME_SLICE') {
195      this.sliceSelected(selection.id);
196    } else {
197      globals.publish('ConnectedFlows', []);
198    }
199
200    if (selection && selection.kind === 'AREA') {
201      this.areaSelected(selection.areaId);
202    } else {
203      globals.publish('SelectedFlows', []);
204    }
205  }
206
207  run() {
208    this.refreshVisibleFlows();
209  }
210}
211