• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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