Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC: capture script attribution data passed via performance.mark #192

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
13 changes: 13 additions & 0 deletions front_end/models/trace/handlers/UserTimingsHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {HandlerState} from './types.js';
let syntheticEvents: Types.Events.SyntheticEventPair<Types.Events.PairableAsync>[] = [];
const performanceMeasureEvents: Types.Events.PerformanceMeasure[] = [];
const performanceMarkEvents: Types.Events.PerformanceMark[] = [];
const performanceAttributionEvents: Types.Events.PerformanceAttribution[] = [];

const consoleTimings: (Types.Events.ConsoleTimeBegin|Types.Events.ConsoleTimeEnd)[] = [];

Expand Down Expand Up @@ -42,13 +43,20 @@ export interface UserTimingsData {
* https://developer.mozilla.org/en-US/docs/Web/API/console/timeStamp
*/
timestampEvents: readonly Types.Events.TimeStamp[];

/**
* Attribution events triggered with the performance.mark() API
* https://developer.mozilla.org/en-US/docs/Web/API/Performance/attribution
*/
performanceAttributions: readonly Types.Events.PerformanceAttribution[];
}
let handlerState = HandlerState.UNINITIALIZED;

export function reset(): void {
syntheticEvents.length = 0;
performanceMeasureEvents.length = 0;
performanceMarkEvents.length = 0;
performanceAttributionEvents.length = 0;
consoleTimings.length = 0;
timestampEvents.length = 0;
handlerState = HandlerState.INITIALIZED;
Expand Down Expand Up @@ -157,6 +165,10 @@ export function handleEvent(event: Types.Events.Event): void {
}
if (Types.Events.isPerformanceMark(event)) {
performanceMarkEvents.push(event);

if (Types.Events.isPerformanceAttribution(event)) {
performanceAttributionEvents.push(event);
}
}
if (Types.Events.isConsoleTime(event)) {
consoleTimings.push(event);
Expand Down Expand Up @@ -189,5 +201,6 @@ export function data(): UserTimingsData {
// TODO(crbug/41484172): UserTimingsHandler.test.ts fails if this is not copied.
performanceMarks: [...performanceMarkEvents],
timestampEvents: [...timestampEvents],
performanceAttributions: [...performanceAttributionEvents],
};
}
13 changes: 13 additions & 0 deletions front_end/models/trace/types/TraceEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export interface ArgsData {
url?: string;
navigationId?: string;
frame?: string;
attribution?: string;
}

export interface CallFrame {
Expand Down Expand Up @@ -1284,6 +1285,14 @@ export interface PerformanceMark extends UserTiming {
ph: Phase.INSTANT|Phase.MARK|Phase.ASYNC_NESTABLE_INSTANT;
}

export interface PerformanceAttribution extends UserTiming {
args: Args&{
data: ArgsData & {
detail: string,
},
};
}

export interface ConsoleTimeBegin extends PairableAsyncBegin {
cat: 'blink.console';
}
Expand Down Expand Up @@ -2132,6 +2141,10 @@ export function isPerformanceMark(event: Event): event is PerformanceMark {
return isUserTiming(event) && (event.ph === Phase.MARK || event.ph === Phase.INSTANT);
}

export function isPerformanceAttribution(event: Event): event is PerformanceAttribution {
return event.name.startsWith('attribution::');
}

export function isConsoleTime(event: Event): event is ConsoleTime {
return event.cat === 'blink.console' && isPhaseAsync(event.ph);
}
Expand Down
12 changes: 12 additions & 0 deletions front_end/panels/freestyler/AiAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

import * as Host from '../../core/host/host.js';
import { TimelineUIUtils } from '../timeline/TimelineUIUtils.js';

export const enum ResponseType {
CONTEXT = 'context',
Expand Down Expand Up @@ -375,6 +376,17 @@ STOP`;
yield response;
}

// Potentially enhance the query with Attribution context.
if (options.selected?.selectedNode?.id === 'EvaluateScript' || options.selected?.selectedNode?.id === 'CompileScript') {
const url = options.selected?.selectedNode?.event?.args?.data?.url;
if (url) {
const attribution = TimelineUIUtils.getAttributionForUrl(url, [...options.selected.parsedTrace.UserTimings.performanceAttributions]);
if (attribution) {
query = `${query}\n\nNote:Attribution for this source: ${attribution}`;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do the prompt strings need to be localized?

}
}
}

query = await this.enhanceQuery(query, options.selected);

for (let i = 0; i < MAX_STEP; i++) {
Expand Down
56 changes: 56 additions & 0 deletions front_end/panels/timeline/TimelineUIUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ const UIStrings = {
*@description Text for a module, the programming concept
*/
module: 'Module',

/**
*@description Label for a group of JavaScript files
*/
Expand Down Expand Up @@ -557,6 +558,22 @@ const UIStrings = {
* @description Label for a string that describes the priority at which a task was scheduled, like 'background' for low-priority tasks, and 'user-blocking' for high priority.
*/
priority: 'Priority',
/**
*@description Text for a performance attribution event
*/
attribution: 'Attribution',
/**
*@description Text for a WordPress core attribution
*/
wordpressCore: 'Core',
/**
*@description Text for a WordPress plugin attribution
*/
wordpressPlugin: 'WordPress Plugin',
/**
*@description Text for a WordPress theme attribution
*/
wordpressTheme: 'WordPress Theme',
};
const str_ = i18n.i18n.registerUIStrings('panels/timeline/TimelineUIUtils.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
Expand Down Expand Up @@ -1209,6 +1226,10 @@ export class TimelineUIUtils {
if (url) {
const {lineNumber, columnNumber} = Trace.Helpers.Trace.getZeroIndexedLineAndColumnForEvent(event);
contentHelper.appendLocationRow(i18nString(UIStrings.script), url, lineNumber || 0, columnNumber);
const attribution = TimelineUIUtils.getAttributionForUrl(url, [...parsedTrace.UserTimings.performanceAttributions]);
if (attribution) {
contentHelper.appendTextRow(i18nString(UIStrings.attribution), attribution);
}
}
const isEager = Boolean(event.args.data?.eager);
if (isEager) {
Expand Down Expand Up @@ -1315,6 +1336,10 @@ export class TimelineUIUtils {
if (url) {
const {lineNumber, columnNumber} = Trace.Helpers.Trace.getZeroIndexedLineAndColumnForEvent(event);
contentHelper.appendLocationRow(i18nString(UIStrings.script), url, lineNumber || 0, columnNumber);
const attribution = TimelineUIUtils.getAttributionForUrl(url, [...parsedTrace.UserTimings.performanceAttributions]);
if (attribution) {
contentHelper.appendTextRow(i18nString(UIStrings.attribution), attribution);
}
}
break;
}
Expand Down Expand Up @@ -1708,6 +1733,37 @@ export class TimelineUIUtils {
return contentHelper.fragment;
}

/**
* Get attribution data for a given URL.
*
* @param {string} url URL to check for attribution data.
* @param {Trace.Types.Events.PerformanceAttribution[]} attributions Array of performance attributions. Immutable.
*
* @return {string|null} Attribution name or null if no attribution is found.
*/
static getAttributionForUrl(url: string, attributions: Trace.Types.Events.PerformanceAttribution[]): string|null {
adamsilverstein marked this conversation as resolved.
Show resolved Hide resolved
const attribution = attributions.find(attribution => {
const detail = JSON.parse(attribution.args.data.detail);
const parsedUrl = new URL(url);
return detail.path.includes(parsedUrl.pathname);
});
if (attribution) {
const detail = JSON.parse(attribution.args.data.detail);

// Determine the type of attribution is based on the attribution name. It can be either a plugin, theme or core enqueue.
let type = '';
if (attribution.name.includes('core')) {
type = UIStrings.wordpressCore;
} else if (attribution.name.includes('plugin')) {
type = UIStrings.wordpressPlugin;
} else if (attribution.name.includes('theme')) {
type = UIStrings.wordpressTheme;
}
return `${type} - ${detail.name}`;
}
return null;
}

static statsForTimeRange(
events: Trace.Types.Events.Event[], startTime: Trace.Types.Timing.MilliSeconds,
endTime: Trace.Types.Timing.MilliSeconds): {
Expand Down