• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2021 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this 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 {assertExists} from '../../base/logging';
16import {PerfettoPlugin} from '../../public/plugin';
17import {Trace} from '../../public/trace';
18import {COUNTER_TRACK_KIND, SLICE_TRACK_KIND} from '../../public/track_kinds';
19import {getTrackName} from '../../public/utils';
20import {TrackNode} from '../../public/workspace';
21import {NUM, NUM_NULL, STR, STR_NULL} from '../../trace_processor/query_result';
22import ProcessThreadGroupsPlugin from '../dev.perfetto.ProcessThreadGroups';
23import StandardGroupsPlugin from '../dev.perfetto.StandardGroups';
24import {SLICE_TRACK_SCHEMAS} from './slice_tracks';
25import {TraceProcessorCounterTrack} from './trace_processor_counter_track';
26import {COUNTER_TRACK_SCHEMAS} from './counter_tracks';
27import {createTraceProcessorSliceTrack} from './trace_processor_slice_track';
28import {TopLevelTrackGroup, TrackGroupSchema} from './types';
29import {removeFalsyValues} from '../../base/array_utils';
30
31export default class implements PerfettoPlugin {
32  static readonly id = 'dev.perfetto.TraceProcessorTrack';
33  static readonly dependencies = [
34    ProcessThreadGroupsPlugin,
35    StandardGroupsPlugin,
36  ];
37
38  private groups = new Map<string, TrackNode>();
39
40  async onTraceLoad(ctx: Trace): Promise<void> {
41    await this.addCounters(ctx);
42    await this.addSlices(ctx);
43  }
44
45  private async addCounters(ctx: Trace) {
46    const result = await ctx.engine.query(`
47      include perfetto module viz.threads;
48
49      with tracks_summary as (
50        select
51          ct.type,
52          ct.name,
53          ct.id,
54          ct.unit,
55          extract_arg(ct.dimension_arg_set_id, 'utid') as utid,
56          extract_arg(ct.dimension_arg_set_id, 'upid') as upid
57        from counter_track ct
58        join _counter_track_summary using (id)
59        order by ct.name
60      )
61      select
62        s.*,
63        thread.tid,
64        thread.name as threadName,
65        ifnull(p.pid, tp.pid) as pid,
66        ifnull(p.name, tp.name) as processName,
67        ifnull(thread.is_main_thread, 0) as isMainThread,
68        ifnull(k.is_kernel_thread, 0) AS isKernelThread
69      from tracks_summary s
70      left join process p on s.upid = p.upid
71      left join thread using (utid)
72      left join _threads_with_kernel_flag k using (utid)
73      left join process tp on thread.upid = tp.upid
74      order by lower(s.name)
75    `);
76
77    const schemas = new Map(COUNTER_TRACK_SCHEMAS.map((x) => [x.type, x]));
78    const it = result.iter({
79      id: NUM,
80      type: STR,
81      name: STR_NULL,
82      unit: STR_NULL,
83      utid: NUM_NULL,
84      upid: NUM_NULL,
85      threadName: STR_NULL,
86      processName: STR_NULL,
87      tid: NUM_NULL,
88      pid: NUM_NULL,
89      isMainThread: NUM,
90      isKernelThread: NUM,
91    });
92    for (; it.valid(); it.next()) {
93      const {
94        type,
95        id: trackId,
96        name,
97        unit,
98        utid,
99        upid,
100        threadName,
101        processName,
102        tid,
103        pid,
104        isMainThread,
105        isKernelThread,
106      } = it;
107      const schema = schemas.get(type);
108      if (schema === undefined) {
109        continue;
110      }
111      const {group, topLevelGroup} = schema;
112      const title = getTrackName({
113        name,
114        tid,
115        threadName,
116        pid,
117        processName,
118        upid,
119        utid,
120        kind: COUNTER_TRACK_KIND,
121        threadTrack: utid !== undefined,
122      });
123      const uri = `/counter_${trackId}`;
124      ctx.tracks.registerTrack({
125        uri,
126        title,
127        tags: {
128          kind: COUNTER_TRACK_KIND,
129          trackIds: [trackId],
130          upid: upid ?? undefined,
131          utid: utid ?? undefined,
132          ...(isKernelThread === 1 && {kernelThread: true}),
133        },
134        chips: removeFalsyValues([
135          isKernelThread === 0 && isMainThread === 1 && 'main thread',
136        ]),
137        track: new TraceProcessorCounterTrack(
138          ctx,
139          uri,
140          {
141            yMode: schema.mode,
142            yRangeSharingKey: schema.shareYAxis ? it.type : undefined,
143            unit: unit ?? undefined,
144          },
145          trackId,
146          title,
147        ),
148      });
149      this.addTrack(
150        ctx,
151        topLevelGroup,
152        group,
153        upid,
154        utid,
155        new TrackNode({
156          uri,
157          title,
158          sortOrder: utid !== undefined || upid !== undefined ? 30 : 0,
159        }),
160      );
161    }
162  }
163
164  private async addSlices(ctx: Trace) {
165    const result = await ctx.engine.query(`
166      include perfetto module viz.threads;
167
168      with grouped as materialized (
169        select
170          t.type,
171          t.name,
172          extract_arg(t.dimension_arg_set_id, 'utid') as utid,
173          extract_arg(t.dimension_arg_set_id, 'upid') as upid,
174          group_concat(t.id) as trackIds,
175          count() as trackCount
176        from _slice_track_summary s
177        join track t using (id)
178        group by type, upid, utid, name
179      )
180      select
181        s.type,
182        s.name,
183        s.utid,
184        ifnull(s.upid, tp.upid) as upid,
185        s.trackIds as trackIds,
186        __max_layout_depth(s.trackCount, s.trackIds) as maxDepth,
187        thread.tid,
188        thread.name as threadName,
189        ifnull(p.pid, tp.pid) as pid,
190        ifnull(p.name, tp.name) as processName,
191        ifnull(thread.is_main_thread, 0) as isMainThread,
192        ifnull(k.is_kernel_thread, 0) AS isKernelThread
193      from grouped s
194      left join process p on s.upid = p.upid
195      left join thread using (utid)
196      left join _threads_with_kernel_flag k using (utid)
197      left join process tp on thread.upid = tp.upid
198      order by lower(s.name)
199    `);
200
201    const schemas = new Map(SLICE_TRACK_SCHEMAS.map((x) => [x.type, x]));
202    const it = result.iter({
203      type: STR,
204      name: STR_NULL,
205      utid: NUM_NULL,
206      upid: NUM_NULL,
207      trackIds: STR,
208      maxDepth: NUM,
209      tid: NUM_NULL,
210      threadName: STR_NULL,
211      pid: NUM_NULL,
212      processName: STR_NULL,
213      isMainThread: NUM,
214      isKernelThread: NUM,
215    });
216    for (; it.valid(); it.next()) {
217      const {
218        trackIds: rawTrackIds,
219        type,
220        name,
221        maxDepth,
222        utid,
223        upid,
224        threadName,
225        processName,
226        tid,
227        pid,
228        isMainThread,
229        isKernelThread,
230      } = it;
231      const schema = schemas.get(type);
232      if (schema === undefined) {
233        continue;
234      }
235      const trackIds = rawTrackIds.split(',').map((v) => Number(v));
236      const {group, topLevelGroup} = schema;
237      const title = getTrackName({
238        name,
239        tid,
240        threadName,
241        pid,
242        processName,
243        upid,
244        utid,
245        kind: SLICE_TRACK_KIND,
246        threadTrack: utid !== undefined,
247      });
248      const uri = `/slice_${trackIds[0]}`;
249      ctx.tracks.registerTrack({
250        uri,
251        title,
252        tags: {
253          kind: SLICE_TRACK_KIND,
254          trackIds: trackIds,
255          upid: upid ?? undefined,
256          utid: utid ?? undefined,
257          ...(isKernelThread === 1 && {kernelThread: true}),
258        },
259        chips: removeFalsyValues([
260          isKernelThread === 0 && isMainThread === 1 && 'main thread',
261        ]),
262        track: createTraceProcessorSliceTrack({
263          trace: ctx,
264          uri,
265          maxDepth,
266          trackIds,
267        }),
268      });
269      this.addTrack(
270        ctx,
271        topLevelGroup,
272        group,
273        upid,
274        utid,
275        new TrackNode({
276          uri,
277          title,
278          sortOrder: utid !== undefined || upid !== undefined ? 20 : 0,
279        }),
280      );
281    }
282  }
283
284  private addTrack(
285    ctx: Trace,
286    topLevelGroup: TopLevelTrackGroup,
287    group: string | TrackGroupSchema | undefined,
288    upid: number | null,
289    utid: number | null,
290    track: TrackNode,
291  ) {
292    switch (topLevelGroup) {
293      case 'PROCESS': {
294        const process = assertExists(
295          ctx.plugins
296            .getPlugin(ProcessThreadGroupsPlugin)
297            .getGroupForProcess(assertExists(upid)),
298        );
299        this.getGroupByName(process, group, upid).addChildInOrder(track);
300        break;
301      }
302      case 'THREAD': {
303        const thread = assertExists(
304          ctx.plugins
305            .getPlugin(ProcessThreadGroupsPlugin)
306            .getGroupForThread(assertExists(utid)),
307        );
308        this.getGroupByName(thread, group, utid).addChildInOrder(track);
309        break;
310      }
311      case undefined: {
312        this.getGroupByName(ctx.workspace.tracks, group, upid).addChildInOrder(
313          track,
314        );
315        break;
316      }
317      default: {
318        const standardGroup = ctx.plugins
319          .getPlugin(StandardGroupsPlugin)
320          .getOrCreateStandardGroup(ctx.workspace, topLevelGroup);
321        this.getGroupByName(standardGroup, group, null).addChildInOrder(track);
322        break;
323      }
324    }
325  }
326
327  private getGroupByName(
328    node: TrackNode,
329    group: string | TrackGroupSchema | undefined,
330    scopeId: number | null,
331  ) {
332    if (group === undefined) {
333      return node;
334    }
335    // This is potentially dangerous - ids MUST be unique within the entire
336    // workspace - this seems to indicate that we could end up duplicating ids in
337    // different nodes.
338    const name = typeof group === 'string' ? group : group.name;
339    const expanded =
340      typeof group === 'string' ? false : group.expanded ?? false;
341    const groupId = `tp_group_${scopeId}_${name.toLowerCase().replace(' ', '_')}`;
342    const groupNode = this.groups.get(groupId);
343    if (groupNode) {
344      return groupNode;
345    }
346    const newGroup = new TrackNode({
347      uri: `/${group}`,
348      isSummary: true,
349      title: name,
350      collapsed: !expanded,
351    });
352    node.addChildInOrder(newGroup);
353    this.groups.set(groupId, newGroup);
354    return newGroup;
355  }
356}
357