xref: /aosp_15_r20/external/perfetto/ui/src/core_plugins/global_groups/index.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1// Copyright (C) 2024 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 {PerfettoPlugin} from '../../public/plugin';
16import {Trace} from '../../public/trace';
17import {TrackNode} from '../../public/workspace';
18
19const MEM_DMA_COUNTER_NAME = 'mem.dma_heap';
20const MEM_DMA = 'mem.dma_buffer';
21const MEM_ION = 'mem.ion';
22const F2FS_IOSTAT_TAG = 'f2fs_iostat.';
23const F2FS_IOSTAT_GROUP_NAME = 'f2fs_iostat';
24const F2FS_IOSTAT_LAT_TAG = 'f2fs_iostat_latency.';
25const F2FS_IOSTAT_LAT_GROUP_NAME = 'f2fs_iostat_latency';
26const DISK_IOSTAT_TAG = 'diskstat.';
27const DISK_IOSTAT_GROUP_NAME = 'diskstat';
28const BUDDY_INFO_TAG = 'mem.buddyinfo';
29const UFS_CMD_TAG_REGEX = new RegExp('^io.ufs.command.tag.*$');
30const UFS_CMD_TAG_GROUP = 'io.ufs.command.tags';
31// NB: Userspace wakelocks start with "WakeLock" not "Wakelock".
32const KERNEL_WAKELOCK_REGEX = new RegExp('^Wakelock.*$');
33const KERNEL_WAKELOCK_GROUP = 'Kernel wakelocks';
34const NETWORK_TRACK_REGEX = new RegExp('^.* (Received|Transmitted)( KB)?$');
35const NETWORK_TRACK_GROUP = 'Networking';
36const ENTITY_RESIDENCY_REGEX = new RegExp('^Entity residency:');
37const ENTITY_RESIDENCY_GROUP = 'Entity residency';
38const UCLAMP_REGEX = new RegExp('^UCLAMP_');
39const UCLAMP_GROUP = 'Scheduler Utilization Clamping';
40const POWER_RAILS_GROUP = 'Power Rails';
41const POWER_RAILS_REGEX = new RegExp('^power.');
42const FREQUENCY_GROUP = 'Frequency Scaling';
43const TEMPERATURE_REGEX = new RegExp('^.* Temperature$');
44const TEMPERATURE_GROUP = 'Temperature';
45const IRQ_GROUP = 'IRQs';
46const IRQ_REGEX = new RegExp('^(Irq|SoftIrq) Cpu.*');
47const CHROME_TRACK_REGEX = new RegExp('^Chrome.*|^InputLatency::.*');
48const CHROME_TRACK_GROUP = 'Chrome Global Tracks';
49const MISC_GROUP = 'Misc Global Tracks';
50
51// This plugin is responsible for organizing all the global tracks.
52export default class implements PerfettoPlugin {
53  static readonly id = 'perfetto.GlobalGroups';
54  async onTraceLoad(trace: Trace): Promise<void> {
55    trace.onTraceReady.addListener(() => {
56      groupGlobalIonTracks(trace);
57      groupGlobalIostatTracks(trace, F2FS_IOSTAT_TAG, F2FS_IOSTAT_GROUP_NAME);
58      groupGlobalIostatTracks(
59        trace,
60        F2FS_IOSTAT_LAT_TAG,
61        F2FS_IOSTAT_LAT_GROUP_NAME,
62      );
63      groupGlobalIostatTracks(trace, DISK_IOSTAT_TAG, DISK_IOSTAT_GROUP_NAME);
64      groupTracksByRegex(trace, UFS_CMD_TAG_REGEX, UFS_CMD_TAG_GROUP);
65      groupGlobalBuddyInfoTracks(trace);
66      groupTracksByRegex(trace, KERNEL_WAKELOCK_REGEX, KERNEL_WAKELOCK_GROUP);
67      groupTracksByRegex(trace, NETWORK_TRACK_REGEX, NETWORK_TRACK_GROUP);
68      groupTracksByRegex(trace, ENTITY_RESIDENCY_REGEX, ENTITY_RESIDENCY_GROUP);
69      groupTracksByRegex(trace, UCLAMP_REGEX, UCLAMP_GROUP);
70      groupFrequencyTracks(trace, FREQUENCY_GROUP);
71      groupTracksByRegex(trace, POWER_RAILS_REGEX, POWER_RAILS_GROUP);
72      groupTracksByRegex(trace, TEMPERATURE_REGEX, TEMPERATURE_GROUP);
73      groupTracksByRegex(trace, IRQ_REGEX, IRQ_GROUP);
74      groupTracksByRegex(trace, CHROME_TRACK_REGEX, CHROME_TRACK_GROUP);
75      groupMiscNonAllowlistedTracks(trace, MISC_GROUP);
76
77      // Move groups underneath tracks
78      Array.from(trace.workspace.children)
79        .sort((a, b) => {
80          // Get the index in the order array
81          const indexA = a.hasChildren ? 1 : 0;
82          const indexB = b.hasChildren ? 1 : 0;
83          return indexA - indexB;
84        })
85        .forEach((n) => trace.workspace.addChildLast(n));
86
87      // If there is only one group, expand it
88      const rootLevelChildren = trace.workspace.children;
89      if (rootLevelChildren.length === 1 && rootLevelChildren[0].hasChildren) {
90        rootLevelChildren[0].expand();
91      }
92    });
93  }
94}
95
96function groupGlobalIonTracks(trace: Trace): void {
97  const ionTracks: TrackNode[] = [];
98  let hasSummary = false;
99
100  for (const track of trace.workspace.children) {
101    if (track.hasChildren) continue;
102
103    const isIon = track.title.startsWith(MEM_ION);
104    const isIonCounter = track.title === MEM_ION;
105    const isDmaHeapCounter = track.title === MEM_DMA_COUNTER_NAME;
106    const isDmaBuffferSlices = track.title === MEM_DMA;
107    if (isIon || isIonCounter || isDmaHeapCounter || isDmaBuffferSlices) {
108      ionTracks.push(track);
109    }
110    hasSummary = hasSummary || isIonCounter;
111    hasSummary = hasSummary || isDmaHeapCounter;
112  }
113
114  if (ionTracks.length === 0 || !hasSummary) {
115    return;
116  }
117
118  const tracksToAddToGroup: TrackNode[] = [];
119  let memGroupNode: TrackNode | undefined;
120  for (const track of ionTracks) {
121    if (
122      !memGroupNode &&
123      [MEM_DMA_COUNTER_NAME, MEM_ION].includes(track.title)
124    ) {
125      // Create a new group that copies the details from this track
126      memGroupNode = new TrackNode({
127        uri: track.uri,
128        title: track.title,
129        isSummary: true,
130      });
131      // Remove it from the workspace as we're going to add the group later
132      track.remove();
133    } else {
134      tracksToAddToGroup.push(track);
135    }
136  }
137
138  if (memGroupNode) {
139    tracksToAddToGroup.forEach((t) => memGroupNode.addChildInOrder(t));
140    trace.workspace.addChildInOrder(memGroupNode);
141  }
142}
143
144function groupGlobalIostatTracks(
145  trace: Trace,
146  tag: string,
147  groupName: string,
148): void {
149  const devMap = new Map<string, TrackNode>();
150
151  for (const track of trace.workspace.children) {
152    if (track.hasChildren) continue;
153    if (track.title.startsWith(tag)) {
154      const name = track.title.split('.', 3);
155      const key = name[1];
156
157      let parentGroup = devMap.get(key);
158      if (!parentGroup) {
159        const group = new TrackNode({title: groupName, isSummary: true});
160        trace.workspace.addChildInOrder(group);
161        devMap.set(key, group);
162        parentGroup = group;
163      }
164
165      track.title = name[2];
166      parentGroup.addChildInOrder(track);
167    }
168  }
169}
170
171function groupGlobalBuddyInfoTracks(trace: Trace): void {
172  const devMap = new Map<string, TrackNode>();
173
174  for (const track of trace.workspace.children) {
175    if (track.hasChildren) continue;
176    if (track.title.startsWith(BUDDY_INFO_TAG)) {
177      const tokens = track.title.split('[');
178      const node = tokens[1].slice(0, -1);
179      const zone = tokens[2].slice(0, -1);
180      const size = tokens[3].slice(0, -1);
181
182      const groupName = 'Buddyinfo:  Node: ' + node + ' Zone: ' + zone;
183      if (!devMap.has(groupName)) {
184        const group = new TrackNode({title: groupName, isSummary: true});
185        devMap.set(groupName, group);
186        trace.workspace.addChildInOrder(group);
187      }
188      track.title = 'Chunk size: ' + size;
189      const group = devMap.get(groupName)!;
190      group.addChildInOrder(track);
191    }
192  }
193}
194
195function groupFrequencyTracks(trace: Trace, groupName: string): void {
196  const group = new TrackNode({title: groupName, isSummary: true});
197
198  for (const track of trace.workspace.children) {
199    if (track.hasChildren) continue;
200    // Group all the frequency tracks together (except the CPU and GPU
201    // frequency ones).
202    if (
203      track.title.endsWith('Frequency') &&
204      !track.title.startsWith('Cpu') &&
205      !track.title.startsWith('Gpu')
206    ) {
207      group.addChildInOrder(track);
208    }
209  }
210
211  if (group.children.length > 0) {
212    trace.workspace.addChildInOrder(group);
213  }
214}
215
216function groupMiscNonAllowlistedTracks(trace: Trace, groupName: string): void {
217  // List of allowlisted track names.
218  const ALLOWLIST_REGEXES = [
219    new RegExp('^Cpu .*$', 'i'),
220    new RegExp('^Gpu .*$', 'i'),
221    new RegExp('^Trace Triggers$'),
222    new RegExp('^Android App Startups$'),
223    new RegExp('^Device State.*$'),
224    new RegExp('^Android logs$'),
225  ];
226
227  const group = new TrackNode({title: groupName, isSummary: true});
228  for (const track of trace.workspace.children) {
229    if (track.hasChildren) continue;
230    let allowlisted = false;
231    for (const regex of ALLOWLIST_REGEXES) {
232      allowlisted = allowlisted || regex.test(track.title);
233    }
234    if (allowlisted) {
235      continue;
236    }
237    group.addChildInOrder(track);
238  }
239
240  if (group.children.length > 0) {
241    trace.workspace.addChildInOrder(group);
242  }
243}
244
245function groupTracksByRegex(
246  trace: Trace,
247  regex: RegExp,
248  groupName: string,
249): void {
250  const group = new TrackNode({title: groupName, isSummary: true});
251
252  for (const track of trace.workspace.children) {
253    if (track.hasChildren) continue;
254    if (regex.test(track.title)) {
255      group.addChildInOrder(track);
256    }
257  }
258
259  if (group.children.length > 0) {
260    trace.workspace.addChildInOrder(group);
261  }
262}
263