blob: 41ca7f8148b7934b1ba69d5b3fb182742318de29 [file] [log] [blame] [edit]
// Copyright (C) 2025 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {Trace} from '../../public/trace';
import {TrackNode} from '../../public/workspace';
import {PerfettoPlugin} from '../../public/plugin';
import StandardGroupsPlugin from '../dev.perfetto.StandardGroups';
import TraceProcessorTrackPlugin from '../dev.perfetto.TraceProcessorTrack';
const NESTING_SEPARATOR: string = ' -> ';
/**
* Organizes tracks with the name containing ' -> ' into a hierarchical
* structure. It creates a copy of the tracks at the end of their parent.
*
* For example, tracks with names:
* - Foo -> Bar -> Track1
* - Foo -> Bar -> Track2
* - Foo -> Track3
*
* Will be reorganized into:
* - Foo
* - Bar
* - Track1
* - Track2
* - Track3
*/
export default class implements PerfettoPlugin {
static readonly id = 'com.android.CreateTrackHierarchyFromNames';
static readonly dependencies = [
StandardGroupsPlugin,
TraceProcessorTrackPlugin,
];
async onTraceLoad(ctx: Trace): Promise<void> {
this.createTrackHierarchyFromNames(ctx);
}
private createTrackHierarchyFromNames(ctx: Trace) {
ctx.defaultWorkspace.flatTracks
.filter((track) => track.name.includes(NESTING_SEPARATOR))
.forEach((track) => this.organizeTrack(track));
}
private organizeTrack(track: TrackNode) {
const name = track.name;
const path = name.split(NESTING_SEPARATOR);
// No path found, no need to organize
if (path.length <= 1) return;
// Root tracks not supported
if (!track.parent) return;
const startFrom = track.parent;
const parentPath = path.slice(0, -1);
const parentNode = this.lookupTrackGroupOrCreate(startFrom, parentPath);
const newName = path.at(-1);
const cloned = new TrackNode({
uri: track.uri,
name: newName,
removable: track.removable,
});
parentNode.addChildLast(cloned);
}
private _nodeCache = new Map<TrackNode, Map<string, TrackNode>>();
private lookupTrackGroupOrCreate(
startFrom: TrackNode,
path: string[],
): TrackNode {
const pathKey = path.join(NESTING_SEPARATOR);
const cachedNode = this._nodeCache.get(startFrom)?.get(pathKey);
if (cachedNode) {
return cachedNode;
}
let currentNode = startFrom;
for (const pathPart of path) {
let nextNode = currentNode.children.find(
(child) => child.name === pathPart,
);
if (!nextNode) {
nextNode = new TrackNode({name: pathPart});
currentNode.addChildLast(nextNode);
}
currentNode = nextNode;
}
const finalNode = currentNode;
let pathMap = this._nodeCache.get(startFrom);
if (!pathMap) {
pathMap = new Map<string, TrackNode>();
this._nodeCache.set(startFrom, pathMap);
}
pathMap.set(pathKey, finalNode);
return finalNode;
}
}