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 19// Name patterns for tracks which will be pinned in the current workspace. 20// It is useful when using the default workspace which contains a lot of tracks 21// and uses up a lot of vertical space. 22const PIN_TRACK_NAME_PATTERNS = [ 23 /NavigationRequest/g, 24 /NavigationStartToBegin/g, 25 /Navigation .*To/g, 26 /WebContentsImpl*/g, 27]; 28 29// Name patterns for tracks that are of interest to navigation investigations. 30// Those will be copied over to specific Chrome Navigations workspace to allow 31// the user to focus only on the tracks of interest. 32const INTERESTING_TRACKS_NAME_PATTERNS = [ 33 /CrBrowserMain*/g, 34 /CrRendererMain*/g, 35 /Navigation.*/g, 36]; 37 38const NAVIGATION_WORKSPACE_NAME = 'Chrome Navigations'; 39 40// Plugin to facilitate inspecting traces generated for Chromium navigation. 41// It allows pinning navigation relevant tracks, focusing on tracks that are 42// of interest, and future ideas for increased productivity. 43export default class implements PerfettoPlugin { 44 static readonly id = 'org.chromium.ChromeNavigation'; 45 46 async onTraceLoad(trace: Trace): Promise<void> { 47 // Command which pins navigation specific tracks to the top of the UI 48 // so they can easily be inspected without scrolling through a lot of 49 // vertical space. 50 trace.commands.registerCommand({ 51 id: 'org.chromium.ChromeNavigation#PinNavigationTracks', 52 name: 'Chrome Navigation: Pin relevant tracks', 53 callback: () => { 54 trace.workspace.flatTracks 55 .filter((t) => PIN_TRACK_NAME_PATTERNS.some((p) => t.title.match(p))) 56 .forEach((t) => t.pin()); 57 }, 58 }); 59 60 // Command which creates a "Chrome Navigations" workspace in which tracks of 61 // interest are displayed and the others are not present. It allows us to 62 // save vertical space and focus the workspace on navigation specific 63 // tracks. 64 // Note: It is important to ensure that all tracks of interest also include 65 // their parent tracks, so we can have the collapsable process/thread 66 // tracks and keep the UI consistent. 67 trace.commands.registerCommand({ 68 id: 'org.chromium.ChromeNavigation#CreateWorkspaceWithTracks', 69 name: `Chrome Navigation: Go to "${NAVIGATION_WORKSPACE_NAME}" workspace`, 70 defaultHotkey: 'Shift+N', 71 callback: () => { 72 const flatIds = new Set<string>(); 73 74 // If the workspace already exists, just switch to it. 75 let ws = trace.workspaces.all.find( 76 (w) => w.title === NAVIGATION_WORKSPACE_NAME, 77 ); 78 if (ws) { 79 trace.workspaces.switchWorkspace(ws); 80 return; 81 } 82 83 // Find all tracks that we want to be visible. 84 trace.workspace.flatTracks 85 .filter((t) => 86 INTERESTING_TRACKS_NAME_PATTERNS.some((p) => t.title.match(p)), 87 ) 88 .forEach((e) => flatIds.add(e.id)); 89 90 // A lambda that will be invoked for each TrackNode to check whehter it 91 // is of interest or is an ancestor of a TrackNode of interest. 92 const visit: (track: TrackNode) => TrackNode | undefined = ( 93 track: TrackNode, 94 ) => { 95 // Visit all children and create track nodes for them if necessary. 96 const children = track.children 97 .map(visit) 98 .filter((t) => t !== undefined); 99 100 // We need to create a new node if we have added any children 101 // or this track itself should be copied because the name matches. 102 const nameMatch = INTERESTING_TRACKS_NAME_PATTERNS.some((p) => 103 track.title.match(p), 104 ); 105 if (children.length === 0 && !nameMatch) { 106 return undefined; 107 } 108 const result = track.clone(); 109 children.forEach((c) => result.addChildInOrder(c)); 110 return result; 111 }; 112 113 // Create the workspace and add all the relevant tracks to it. 114 ws = trace.workspaces.createEmptyWorkspace(NAVIGATION_WORKSPACE_NAME); 115 for (const track of trace.workspace.children) { 116 const maybeTrack = visit(track); 117 if (maybeTrack !== undefined) { 118 ws.addChildInOrder(maybeTrack); 119 } 120 } 121 122 // Expand all the tracks, so they are visible by default. It can be done 123 // from the UI easily, but saves the user a mouse move and a click ;). 124 ws.flatTracks.forEach((t) => t.expand()); 125 126 trace.workspaces.switchWorkspace(ws); 127 }, 128 }); 129 } 130} 131