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