• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2025 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 {Trace} from '../../public/trace';
16import {PerfettoPlugin} from '../../public/plugin';
17import ProcessThreadGroupsPlugin from '../dev.perfetto.ProcessThreadGroups';
18import TraceProcessorTrackPlugin from '../dev.perfetto.TraceProcessorTrack';
19import {NUM, NUM_NULL, STR, STR_NULL} from '../../trace_processor/query_result';
20import {TrackNode} from '../../public/workspace';
21import {assertExists, assertTrue} from '../../base/logging';
22import {COUNTER_TRACK_KIND, SLICE_TRACK_KIND} from '../../public/track_kinds';
23import {createTraceProcessorSliceTrack} from '../dev.perfetto.TraceProcessorTrack/trace_processor_slice_track';
24import {TraceProcessorCounterTrack} from '../dev.perfetto.TraceProcessorTrack/trace_processor_counter_track';
25import {getTrackName} from '../../public/utils';
26
27export default class implements PerfettoPlugin {
28  static readonly id = 'dev.perfetto.TrackEvent';
29  static readonly dependencies = [
30    ProcessThreadGroupsPlugin,
31    TraceProcessorTrackPlugin,
32  ];
33
34  private parentTrackNodes = new Map<string, TrackNode>();
35
36  async onTraceLoad(ctx: Trace): Promise<void> {
37    const res = await ctx.engine.query(`
38      include perfetto module viz.summary.track_event;
39      select
40        ifnull(g.upid, t.upid) as upid,
41        g.utid,
42        g.parent_id as parentId,
43        g.is_counter AS isCounter,
44        g.name,
45        g.unit,
46        g.builtin_counter_type as builtinCounterType,
47        g.has_data AS hasData,
48        g.has_children AS hasChildren,
49        g.track_ids as trackIds,
50        g.order_id as orderId,
51        t.name as threadName,
52        t.tid as tid,
53        ifnull(p.pid, tp.pid) as pid,
54        ifnull(p.name, tp.name) as processName
55      from _track_event_tracks_ordered_groups g
56      left join process p using (upid)
57      left join thread t using (utid)
58      left join process tp on tp.upid = t.upid
59    `);
60    const it = res.iter({
61      upid: NUM_NULL,
62      utid: NUM_NULL,
63      parentId: NUM_NULL,
64      isCounter: NUM,
65      name: STR_NULL,
66      unit: STR_NULL,
67      builtinCounterType: STR_NULL,
68      hasData: NUM,
69      hasChildren: NUM,
70      trackIds: STR,
71      orderId: NUM,
72      threadName: STR_NULL,
73      tid: NUM_NULL,
74      pid: NUM_NULL,
75      processName: STR_NULL,
76    });
77    const processGroupsPlugin = ctx.plugins.getPlugin(
78      ProcessThreadGroupsPlugin,
79    );
80    const trackIdToTrackNode = new Map<number, TrackNode>();
81    for (; it.valid(); it.next()) {
82      const {
83        upid,
84        utid,
85        parentId,
86        isCounter,
87        name,
88        unit,
89        builtinCounterType,
90        hasData,
91        hasChildren,
92        trackIds: rawTrackIds,
93        orderId,
94        threadName,
95        tid,
96        pid,
97        processName,
98      } = it;
99
100      // Don't add track_event tracks which don't have any data and don't have
101      // any children.
102      if (!hasData && !hasChildren) {
103        continue;
104      }
105
106      const kind = isCounter ? COUNTER_TRACK_KIND : SLICE_TRACK_KIND;
107      const trackIds = rawTrackIds.split(',').map((v) => Number(v));
108      const title = getTrackName({
109        name,
110        utid,
111        upid,
112        kind,
113        threadTrack: utid !== null,
114        threadName,
115        processName,
116        tid,
117        pid,
118      });
119      const uri = `/track_event_${trackIds[0]}`;
120      if (hasData && isCounter) {
121        // Don't show any builtin counter.
122        if (builtinCounterType !== null) {
123          continue;
124        }
125        assertTrue(trackIds.length === 1);
126        const trackId = trackIds[0];
127        ctx.tracks.registerTrack({
128          uri,
129          title,
130          tags: {
131            kind,
132            trackIds: [trackIds[0]],
133            upid: upid ?? undefined,
134            utid: utid ?? undefined,
135          },
136          track: new TraceProcessorCounterTrack(
137            ctx,
138            uri,
139            {
140              unit: unit ?? undefined,
141            },
142            trackId,
143            title,
144          ),
145        });
146      } else if (hasData) {
147        ctx.tracks.registerTrack({
148          uri,
149          title,
150          tags: {
151            kind,
152            trackIds: trackIds,
153            upid: upid ?? undefined,
154            utid: utid ?? undefined,
155          },
156          track: createTraceProcessorSliceTrack({trace: ctx, uri, trackIds}),
157        });
158      }
159      const parent = this.findParentTrackNode(
160        ctx,
161        processGroupsPlugin,
162        trackIdToTrackNode,
163        parentId ?? undefined,
164        upid ?? undefined,
165        utid ?? undefined,
166        hasChildren,
167      );
168      const node = new TrackNode({
169        title,
170        sortOrder: orderId,
171        isSummary: hasData === 0,
172        uri: uri,
173      });
174      parent.addChildInOrder(node);
175      trackIdToTrackNode.set(trackIds[0], node);
176    }
177  }
178
179  private findParentTrackNode(
180    ctx: Trace,
181    processGroupsPlugin: ProcessThreadGroupsPlugin,
182    trackIdToTrackNode: Map<number, TrackNode>,
183    parentId: number | undefined,
184    upid: number | undefined,
185    utid: number | undefined,
186    hasChildren: number,
187  ): TrackNode {
188    if (parentId !== undefined) {
189      return assertExists(trackIdToTrackNode.get(parentId));
190    }
191    if (utid !== undefined) {
192      return assertExists(processGroupsPlugin.getGroupForThread(utid));
193    }
194    if (upid !== undefined) {
195      return assertExists(processGroupsPlugin.getGroupForProcess(upid));
196    }
197    if (hasChildren) {
198      return ctx.workspace.tracks;
199    }
200    const id = `/track_event_root`;
201    let node = this.parentTrackNodes.get(id);
202    if (node === undefined) {
203      node = new TrackNode({
204        title: 'Global Track Events',
205        isSummary: true,
206      });
207      ctx.workspace.addChildInOrder(node);
208      this.parentTrackNodes.set(id, node);
209    }
210    return node;
211  }
212}
213