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