blob: 33617f6044ac2ff2365e96341555eff317e06b50 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import type * as CPUProfile from '../../models/cpu_profile/cpu_profile.js';
import {describeWithEnvironment} from '../../testing/EnvironmentHelpers.js';
import {allThreadEntriesInTrace, getMainThread} from '../../testing/TraceHelpers.js';
import {TraceLoader} from '../../testing/TraceLoader.js';
import * as Trace from './trace.js';
describeWithEnvironment('Name', () => {
it('uses the URL for the name of a network request', async function() {
const parsedTrace = await TraceLoader.traceEngine(this, 'web-dev-with-commit.json.gz');
const request = parsedTrace.data.NetworkRequests.byTime.at(0);
assert.isOk(request);
const name = Trace.Name.forEntry(request);
assert.strictEqual(name, 'web.dev/ (web.dev)');
});
it('uses "Frame" for timeline frames', async function() {
const parsedTrace = await TraceLoader.traceEngine(this, 'web-dev-with-commit.json.gz');
const frame = parsedTrace.data.Frames.frames.at(0);
assert.isOk(frame);
const name = Trace.Name.forEntry(frame);
assert.strictEqual(name, 'Frame');
});
it('adds the event type for EventDispatch events', async function() {
const parsedTrace = await TraceLoader.traceEngine(this, 'one-second-interaction.json.gz');
const clickEvent = allThreadEntriesInTrace(parsedTrace).find(event => {
return Trace.Types.Events.isDispatch(event) && event.args.data.type === 'click';
});
assert.isOk(clickEvent);
const name = Trace.Name.forEntry(clickEvent);
assert.strictEqual(name, 'Event: click');
});
it('correctly titles layout shifts', async function() {
const parsedTrace = await TraceLoader.traceEngine(this, 'cls-single-frame.json.gz');
const shifts = parsedTrace.data.LayoutShifts.clusters.flatMap(c => c.events);
const title = Trace.Name.forEntry(shifts[0]);
assert.strictEqual(title, 'Layout shift');
});
it('correctly titles animation events', async function() {
const parsedTrace = await TraceLoader.traceEngine(this, 'animation.json.gz');
const animation = parsedTrace.data.Animations.animations.at(0);
assert.isOk(animation);
const title = Trace.Name.forEntry(animation);
assert.strictEqual(title, 'Animation');
});
it('uses the names defined in the entry styles', async function() {
const parsedTrace = await TraceLoader.traceEngine(this, 'web-dev-with-commit.json.gz');
const entry = allThreadEntriesInTrace(parsedTrace).find(e => e.name === Trace.Types.Events.Name.RUN_TASK);
assert.isOk(entry);
const name = Trace.Name.forEntry(entry, parsedTrace);
assert.strictEqual(name, 'Task');
});
it('returns the name and URL for a WebSocketCreate event', async function() {
const parsedTrace = await TraceLoader.traceEngine(this, 'network-websocket-messages.json.gz');
let createEvent: Trace.Types.Events.WebSocketCreate|null = null;
for (const websocket of parsedTrace.data.NetworkRequests.webSocket) {
for (const event of websocket.events) {
if (Trace.Types.Events.isWebSocketCreate(event)) {
createEvent = event;
break;
}
}
}
assert.isOk(createEvent);
const name = Trace.Name.forEntry(createEvent, parsedTrace);
assert.strictEqual(name, 'WebSocket opened: wss://echo.websocket.org/');
});
it('returns a custom name for WebSocket destroy events', async function() {
const fakeDestroyEvent = {
name: Trace.Types.Events.Name.WEB_SOCKET_DESTROY,
} as unknown as Trace.Types.Events.WebSocketDestroy;
const name = Trace.Name.forEntry(fakeDestroyEvent);
assert.strictEqual(name, 'WebSocket closed');
});
it('returns a custom name for pointer interactions', async function() {
const parsedTrace = await TraceLoader.traceEngine(this, 'slow-interaction-button-click.json.gz');
const firstInteraction = parsedTrace.data.UserInteractions.interactionEvents.at(0);
assert.isOk(firstInteraction);
const name = Trace.Name.forEntry(firstInteraction);
assert.strictEqual(name, 'Pointer');
});
it('returns a custom name for keyboard interactions', async function() {
const parsedTrace = await TraceLoader.traceEngine(this, 'slow-interaction-keydown.json.gz');
const keydownInteraction = parsedTrace.data.UserInteractions.interactionEvents.find(e => e.type === 'keydown');
assert.isOk(keydownInteraction);
const name = Trace.Name.forEntry(keydownInteraction);
assert.strictEqual(name, 'Keyboard');
});
it('returns "other" for unknown interaction event types', async function() {
const parsedTrace = await TraceLoader.traceEngine(this, 'slow-interaction-button-click.json.gz');
// Copy the event so we do not modify the actual trace data, and fake its
// interaction type to be unexpected.
const firstInteraction = {...parsedTrace.data.UserInteractions.interactionEvents[0]};
firstInteraction.type = 'unknown';
const name = Trace.Name.forEntry(firstInteraction);
assert.strictEqual(name, 'Other');
});
describe('profile calls', () => {
it('uses the profile name for a ProfileCall if it has been set', async function() {
const parsedTrace = await TraceLoader.traceEngine(this, 'react-hello-world.json.gz');
const {entry, profileNode} = getProfileEventAndNodeForReactTrace(parsedTrace);
assert.isNull(profileNode.originalFunctionName);
profileNode.setOriginalFunctionName('testing-profile-name');
const name = Trace.Name.forEntry(entry, parsedTrace);
assert.strictEqual(name, 'testing-profile-name');
// Don't impact other tests.
profileNode.setOriginalFunctionName(null);
});
it('falls back to the call frame name if a specific name has not been set', async function() {
const parsedTrace = await TraceLoader.traceEngine(this, 'react-hello-world.json.gz');
const {entry, profileNode} = getProfileEventAndNodeForReactTrace(parsedTrace);
assert.isNull(profileNode.originalFunctionName);
const name = Trace.Name.forEntry(entry, parsedTrace);
assert.strictEqual(name, 'performConcurrentWorkOnRoot');
});
/** Finds a particular event from the react-hello-world trace which is used for our test example. **/
function getProfileEventAndNodeForReactTrace(parsedTrace: Trace.TraceModel.ParsedTrace): {
entry: Trace.Types.Events.SyntheticProfileCall,
profileNode: CPUProfile.ProfileTreeModel.ProfileNode,
} {
const mainThread = getMainThread(parsedTrace.data.Renderer);
let foundNode: CPUProfile.ProfileTreeModel.ProfileNode|null = null;
let foundEntry: Trace.Types.Events.SyntheticProfileCall|null = null;
for (const entry of mainThread.entries) {
if (Trace.Types.Events.isProfileCall(entry) && entry.callFrame.functionName === 'performConcurrentWorkOnRoot') {
const profile = parsedTrace.data.Samples.profilesInProcess.get(entry.pid)?.get(entry.tid);
const node = profile?.parsedProfile.nodeById(entry.nodeId);
if (node) {
foundNode = node;
}
foundEntry = entry;
break;
}
}
if (!foundNode) {
throw new Error('Could not find CPU Profile node.');
}
if (!foundEntry) {
throw new Error('Could not find expected entry.');
}
return {
entry: foundEntry,
profileNode: foundNode,
};
}
});
});