| // 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, |
| }; |
| } |
| }); |
| }); |