• 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 {Engine} from '../common/engine';
16import {featureFlags} from '../common/feature_flags';
17import {NUM, STR_NULL} from '../common/query_result';
18import {Area} from '../common/state';
19import {fromNs, toNs} from '../common/time';
20import {Flow} from '../frontend/globals';
21import {publishConnectedFlows, publishSelectedFlows} from '../frontend/publish';
22import {
23  ACTUAL_FRAMES_SLICE_TRACK_KIND,
24  Config as ActualConfig
25} from '../tracks/actual_frames/common';
26import {
27  Config as SliceConfig,
28  SLICE_TRACK_KIND
29} from '../tracks/chrome_slices/common';
30
31import {Controller} from './controller';
32import {globals} from './globals';
33
34export interface FlowEventsControllerArgs {
35  engine: Engine;
36}
37
38const SHOW_INDIRECT_PRECEDING_FLOWS_FLAG = featureFlags.register({
39  id: 'showIndirectPrecedingFlows',
40  name: 'Show indirect preceding flows',
41  description: 'Show indirect preceding flows (connected through ancestor ' +
42      'slices) when a slice is selected.',
43  defaultValue: false,
44});
45
46
47export class FlowEventsController extends Controller<'main'> {
48  private lastSelectedSliceId?: number;
49  private lastSelectedArea?: Area;
50  private lastSelectedKind: 'CHROME_SLICE'|'AREA'|'NONE' = 'NONE';
51
52  constructor(private args: FlowEventsControllerArgs) {
53    super('main');
54  }
55
56  queryFlowEvents(query: string, callback: (flows: Flow[]) => void) {
57    this.args.engine.query(query).then(result => {
58      const flows: Flow[] = [];
59      const it = result.iter({
60        beginSliceId: NUM,
61        beginTrackId: NUM,
62        beginSliceName: STR_NULL,
63        beginSliceCategory: STR_NULL,
64        beginSliceStartTs: NUM,
65        beginSliceEndTs: NUM,
66        beginDepth: NUM,
67        beginThreadName: STR_NULL,
68        beginProcessName: STR_NULL,
69        endSliceId: NUM,
70        endTrackId: NUM,
71        endSliceName: STR_NULL,
72        endSliceCategory: STR_NULL,
73        endSliceStartTs: NUM,
74        endSliceEndTs: NUM,
75        endDepth: NUM,
76        endThreadName: STR_NULL,
77        endProcessName: STR_NULL,
78        name: STR_NULL,
79        category: STR_NULL,
80        id: NUM,
81      });
82      for (; it.valid(); it.next()) {
83        const beginSliceId = it.beginSliceId;
84        const beginTrackId = it.beginTrackId;
85        const beginSliceName =
86            it.beginSliceName === null ? 'NULL' : it.beginSliceName;
87        const beginSliceCategory =
88            it.beginSliceCategory === null ? 'NULL' : it.beginSliceCategory;
89        const beginSliceStartTs = fromNs(it.beginSliceStartTs);
90        const beginSliceEndTs = fromNs(it.beginSliceEndTs);
91        const beginDepth = it.beginDepth;
92        const beginThreadName =
93            it.beginThreadName === null ? 'NULL' : it.beginThreadName;
94        const beginProcessName =
95            it.beginProcessName === null ? 'NULL' : it.beginProcessName;
96
97        const endSliceId = it.endSliceId;
98        const endTrackId = it.endTrackId;
99        const endSliceName =
100            it.endSliceName === null ? 'NULL' : it.endSliceName;
101        const endSliceCategory =
102            it.endSliceCategory === null ? 'NULL' : it.endSliceCategory;
103        const endSliceStartTs = fromNs(it.endSliceStartTs);
104        const endSliceEndTs = fromNs(it.endSliceEndTs);
105        const endDepth = it.endDepth;
106        const endThreadName =
107            it.endThreadName === null ? 'NULL' : it.endThreadName;
108        const endProcessName =
109            it.endProcessName === null ? 'NULL' : it.endProcessName;
110
111        // Category and name present only in version 1 flow events
112        // It is most likelly NULL for all other versions
113        const category = it.category === null ? undefined : it.category;
114        const name = it.name === null ? undefined : it.name;
115        const id = it.id;
116
117        flows.push({
118          id,
119          begin: {
120            trackId: beginTrackId,
121            sliceId: beginSliceId,
122            sliceName: beginSliceName,
123            sliceCategory: beginSliceCategory,
124            sliceStartTs: beginSliceStartTs,
125            sliceEndTs: beginSliceEndTs,
126            depth: beginDepth,
127            threadName: beginThreadName,
128            processName: beginProcessName
129          },
130          end: {
131            trackId: endTrackId,
132            sliceId: endSliceId,
133            sliceName: endSliceName,
134            sliceCategory: endSliceCategory,
135            sliceStartTs: endSliceStartTs,
136            sliceEndTs: endSliceEndTs,
137            depth: endDepth,
138            threadName: endThreadName,
139            processName: endProcessName
140          },
141          dur: endSliceStartTs - beginSliceEndTs,
142          category,
143          name
144        });
145      }
146      callback(flows);
147    });
148  }
149
150  sliceSelected(sliceId: number) {
151    if (this.lastSelectedKind === 'CHROME_SLICE' &&
152        this.lastSelectedSliceId === sliceId) {
153      return;
154    }
155    this.lastSelectedSliceId = sliceId;
156    this.lastSelectedKind = 'CHROME_SLICE';
157
158    const connectedFlows = SHOW_INDIRECT_PRECEDING_FLOWS_FLAG.get() ?
159        `(
160           select * from directly_connected_flow(${sliceId})
161           union
162           select * from preceding_flow(${sliceId})
163         )` :
164        `directly_connected_flow(${sliceId})`;
165
166    const query = `
167    select
168      f.slice_out as beginSliceId,
169      t1.track_id as beginTrackId,
170      t1.name as beginSliceName,
171      t1.category as beginSliceCategory,
172      t1.ts as beginSliceStartTs,
173      (t1.ts+t1.dur) as beginSliceEndTs,
174      t1.depth as beginDepth,
175      (thread_out.name || ' ' || thread_out.tid) as beginThreadName,
176      (process_out.name || ' ' || process_out.pid) as beginProcessName,
177      f.slice_in as endSliceId,
178      t2.track_id as endTrackId,
179      t2.name as endSliceName,
180      t2.category as endSliceCategory,
181      t2.ts as endSliceStartTs,
182      (t2.ts+t2.dur) as endSliceEndTs,
183      t2.depth as endDepth,
184      (thread_in.name || ' ' || thread_in.tid) as endThreadName,
185      (process_in.name || ' ' || process_in.pid) as endProcessName,
186      extract_arg(f.arg_set_id, 'cat') as category,
187      extract_arg(f.arg_set_id, 'name') as name,
188      f.id as id
189    from ${connectedFlows} f
190    join slice t1 on f.slice_out = t1.slice_id
191    join slice t2 on f.slice_in = t2.slice_id
192    left join thread_track track_out on track_out.id = t1.track_id
193    left join thread thread_out on thread_out.utid = track_out.utid
194    left join thread_track track_in on track_in.id = t2.track_id
195    left join thread thread_in on thread_in.utid = track_in.utid
196    left join process process_out on process_out.upid = thread_out.upid
197    left join process process_in on process_in.upid = thread_in.upid
198    `;
199    this.queryFlowEvents(
200        query, (flows: Flow[]) => publishConnectedFlows(flows));
201  }
202
203  areaSelected(areaId: string) {
204    const area = globals.state.areas[areaId];
205    if (this.lastSelectedKind === 'AREA' && this.lastSelectedArea &&
206        this.lastSelectedArea.tracks.join(',') === area.tracks.join(',') &&
207        this.lastSelectedArea.endSec === area.endSec &&
208        this.lastSelectedArea.startSec === area.startSec) {
209      return;
210    }
211
212    this.lastSelectedArea = area;
213    this.lastSelectedKind = 'AREA';
214
215    const trackIds: number[] = [];
216
217    for (const uiTrackId of area.tracks) {
218      const track = globals.state.tracks[uiTrackId];
219      if (track === undefined) {
220        continue;
221      }
222      if (track.kind === SLICE_TRACK_KIND) {
223        trackIds.push((track.config as SliceConfig).trackId);
224      } else if (track.kind === ACTUAL_FRAMES_SLICE_TRACK_KIND) {
225        const actualConfig = track.config as ActualConfig;
226        for (const trackId of actualConfig.trackIds) {
227          trackIds.push(trackId);
228        }
229      }
230    }
231
232    const tracks = `(${trackIds.join(',')})`;
233
234    const startNs = toNs(area.startSec);
235    const endNs = toNs(area.endSec);
236
237    const query = `
238    select
239      f.slice_out as beginSliceId,
240      t1.track_id as beginTrackId,
241      t1.name as beginSliceName,
242      t1.category as beginSliceCategory,
243      t1.ts as beginSliceStartTs,
244      (t1.ts+t1.dur) as beginSliceEndTs,
245      t1.depth as beginDepth,
246      NULL as beginThreadName,
247      NULL as beginProcessName,
248      f.slice_in as endSliceId,
249      t2.track_id as endTrackId,
250      t2.name as endSliceName,
251      t2.category as endSliceCategory,
252      t2.ts as endSliceStartTs,
253      (t2.ts+t2.dur) as endSliceEndTs,
254      t2.depth as endDepth,
255      NULL as endThreadName,
256      NULL as endProcessName,
257      extract_arg(f.arg_set_id, 'cat') as category,
258      extract_arg(f.arg_set_id, 'name') as name,
259      f.id as id
260    from flow f
261    join slice t1 on f.slice_out = t1.slice_id
262    join slice t2 on f.slice_in = t2.slice_id
263    where
264      (t1.track_id in ${tracks}
265        and (t1.ts+t1.dur <= ${endNs} and t1.ts+t1.dur >= ${startNs}))
266      or
267      (t2.track_id in ${tracks}
268        and (t2.ts <= ${endNs} and t2.ts >= ${startNs}))
269    `;
270    this.queryFlowEvents(query, (flows: Flow[]) => publishSelectedFlows(flows));
271  }
272
273  refreshVisibleFlows() {
274    const selection = globals.state.currentSelection;
275    if (!selection) {
276      this.lastSelectedKind = 'NONE';
277      publishConnectedFlows([]);
278      publishSelectedFlows([]);
279      return;
280    }
281
282    // TODO(b/155483804): This is a hack as annotation slices don't contain
283    // flows. We should tidy this up when fixing this bug.
284    if (selection && selection.kind === 'CHROME_SLICE' &&
285        selection.table !== 'annotation') {
286      this.sliceSelected(selection.id);
287    } else {
288      publishConnectedFlows([]);
289    }
290
291    if (selection && selection.kind === 'AREA') {
292      this.areaSelected(selection.areaId);
293    } else {
294      publishSelectedFlows([]);
295    }
296  }
297
298  run() {
299    this.refreshVisibleFlows();
300  }
301}
302