Next.js website for Rocky Mountain Vending company featuring: - Product catalog with Stripe integration - Service areas and parts pages - Admin dashboard with Clerk authentication - SEO optimized pages with JSON-LD structured data Co-authored-by: Cursor <cursoragent@cursor.com>
210 lines
No EOL
9.9 KiB
Text
210 lines
No EOL
9.9 KiB
Text
// Copyright 2022 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
import * as Platform from '../../../core/platform/platform.js';
|
|
import * as CPUProfile from '../../cpu_profile/cpu_profile.js';
|
|
import * as Helpers from '../helpers/helpers.js';
|
|
import * as Types from '../types/types.js';
|
|
const events = new Map();
|
|
const profilesInProcess = new Map();
|
|
const entryToNode = new Map();
|
|
// The profile head, containing its metadata like its start
|
|
// time, comes in a "Profile" event. The sample data comes in
|
|
// "ProfileChunk" events. We match these ProfileChunks with their head
|
|
// using process and profile ids. However, in order to integrate sample
|
|
// data with trace data, we need the thread id that owns each profile.
|
|
// This thread id is extracted from the head event.
|
|
// For this reason, we have a preprocessed data structure, where events
|
|
// are matched by profile id, which we then finish processing to export
|
|
// events matched by thread id.
|
|
const preprocessedData = new Map();
|
|
function parseCPUProfileData(parseOptions) {
|
|
for (const [processId, profiles] of preprocessedData) {
|
|
for (const [profileId, preProcessedData] of profiles) {
|
|
const threadId = preProcessedData.threadId;
|
|
if (!preProcessedData.rawProfile.nodes.length || threadId === undefined) {
|
|
continue;
|
|
}
|
|
const indexStack = [];
|
|
const profileModel = new CPUProfile.CPUProfileDataModel.CPUProfileDataModel(preProcessedData.rawProfile);
|
|
const profileTree = Helpers.TreeHelpers.makeEmptyTraceEntryTree();
|
|
profileTree.maxDepth = profileModel.maxDepth;
|
|
const finalizedData = {
|
|
rawProfile: preProcessedData.rawProfile,
|
|
parsedProfile: profileModel,
|
|
profileCalls: [],
|
|
profileTree,
|
|
profileId,
|
|
};
|
|
const dataByThread = Platform.MapUtilities.getWithDefault(profilesInProcess, processId, () => new Map());
|
|
dataByThread.set(threadId, finalizedData);
|
|
// Only need to build pure JS ProfileCalls if we're parsing a CPU Profile, otherwise SamplesIntegrator does the work.
|
|
if (parseOptions.isCPUProfile) {
|
|
buildProfileCallsForCPUProfile();
|
|
}
|
|
function buildProfileCallsForCPUProfile() {
|
|
profileModel.forEachFrame(openFrameCallback, closeFrameCallback);
|
|
function openFrameCallback(depth, node, sampleIndex, timeStampMilliseconds) {
|
|
if (threadId === undefined) {
|
|
return;
|
|
}
|
|
const ts = Helpers.Timing.milliToMicro(Types.Timing.Milli(timeStampMilliseconds));
|
|
const nodeId = node.id;
|
|
const profileCall = Helpers.Trace.makeProfileCall(node, profileId, sampleIndex, ts, processId, threadId);
|
|
finalizedData.profileCalls.push(profileCall);
|
|
indexStack.push(finalizedData.profileCalls.length - 1);
|
|
const traceEntryNode = Helpers.TreeHelpers.makeEmptyTraceEntryNode(profileCall, nodeId);
|
|
entryToNode.set(profileCall, traceEntryNode);
|
|
traceEntryNode.depth = depth;
|
|
if (indexStack.length === 1) {
|
|
// First call in the stack is a root call.
|
|
finalizedData.profileTree?.roots.add(traceEntryNode);
|
|
}
|
|
}
|
|
function closeFrameCallback(_depth, _node, _sampleIndex, _timeStampMillis, durMs, selfTimeMs) {
|
|
const profileCallIndex = indexStack.pop();
|
|
const profileCall = profileCallIndex !== undefined && finalizedData.profileCalls[profileCallIndex];
|
|
if (!profileCall) {
|
|
return;
|
|
}
|
|
const { callFrame, ts, pid, tid } = profileCall;
|
|
const traceEntryNode = entryToNode.get(profileCall);
|
|
if (callFrame === undefined || ts === undefined || pid === undefined || profileId === undefined ||
|
|
tid === undefined || traceEntryNode === undefined) {
|
|
return;
|
|
}
|
|
const dur = Helpers.Timing.milliToMicro(Types.Timing.Milli(durMs));
|
|
const selfTime = Helpers.Timing.milliToMicro(Types.Timing.Milli(selfTimeMs));
|
|
profileCall.dur = dur;
|
|
traceEntryNode.selfTime = selfTime;
|
|
const parentIndex = indexStack.at(-1);
|
|
const parent = parentIndex !== undefined && finalizedData.profileCalls.at(parentIndex);
|
|
const parentNode = parent && entryToNode.get(parent);
|
|
if (!parentNode) {
|
|
return;
|
|
}
|
|
traceEntryNode.parent = parentNode;
|
|
parentNode.children.push(traceEntryNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
export function reset() {
|
|
events.clear();
|
|
preprocessedData.clear();
|
|
profilesInProcess.clear();
|
|
entryToNode.clear();
|
|
}
|
|
export function handleEvent(event) {
|
|
/**
|
|
* A fake trace event created to support CDP.Profiler.Profiles in the
|
|
* trace engine.
|
|
*/
|
|
if (Types.Events.isSyntheticCpuProfile(event)) {
|
|
// At the moment we are attaching to a single node target so we
|
|
// should only get a single CPU profile. The values of the process
|
|
// id and thread id are not really important, so we use the data
|
|
// in the fake event. Should multi-thread CPU profiling be supported
|
|
// we could use these fields in the event to pass thread info.
|
|
const profileData = getOrCreatePreProcessedData(event.pid, event.id);
|
|
profileData.rawProfile = event.args.data.cpuProfile;
|
|
profileData.threadId = event.tid;
|
|
return;
|
|
}
|
|
if (Types.Events.isProfile(event)) {
|
|
// Do not use event.args.data.startTime as it is in CLOCK_MONOTONIC domain,
|
|
// but use profileEvent.ts which has been translated to Perfetto's clock
|
|
// domain. Also convert from ms to us.
|
|
// Note: events are collected on a different thread than what's sampled.
|
|
// The correct process and thread ids are specified by the profile.
|
|
const profileData = getOrCreatePreProcessedData(event.pid, event.id);
|
|
profileData.rawProfile.startTime = event.ts;
|
|
profileData.threadId = event.tid;
|
|
return;
|
|
}
|
|
if (Types.Events.isProfileChunk(event)) {
|
|
const profileData = getOrCreatePreProcessedData(event.pid, event.id);
|
|
const cdpProfile = profileData.rawProfile;
|
|
const nodesAndSamples = event.args?.data?.cpuProfile || { samples: [] };
|
|
const samples = nodesAndSamples?.samples || [];
|
|
const traceIds = event.args?.data?.cpuProfile?.trace_ids;
|
|
for (const n of nodesAndSamples?.nodes || []) {
|
|
const lineNumber = typeof n.callFrame.lineNumber === 'undefined' ? -1 : n.callFrame.lineNumber;
|
|
const columnNumber = typeof n.callFrame.columnNumber === 'undefined' ? -1 : n.callFrame.columnNumber;
|
|
const scriptId = String(n.callFrame.scriptId);
|
|
const url = n.callFrame.url || '';
|
|
const node = {
|
|
...n,
|
|
callFrame: {
|
|
...n.callFrame,
|
|
url,
|
|
lineNumber,
|
|
columnNumber,
|
|
scriptId,
|
|
},
|
|
};
|
|
cdpProfile.nodes.push(node);
|
|
}
|
|
const timeDeltas = event.args.data?.timeDeltas || [];
|
|
const lines = event.args.data?.lines || Array(samples.length).fill(0);
|
|
cdpProfile.samples?.push(...samples);
|
|
cdpProfile.timeDeltas?.push(...timeDeltas);
|
|
cdpProfile.lines?.push(...lines);
|
|
if (traceIds) {
|
|
cdpProfile.traceIds ??= {};
|
|
for (const key in traceIds) {
|
|
cdpProfile.traceIds[key] = traceIds[key];
|
|
}
|
|
}
|
|
if (cdpProfile.samples && cdpProfile.timeDeltas && cdpProfile.samples.length !== cdpProfile.timeDeltas.length) {
|
|
console.error('Failed to parse CPU profile.');
|
|
return;
|
|
}
|
|
if (!cdpProfile.endTime && cdpProfile.timeDeltas) {
|
|
const timeDeltas = cdpProfile.timeDeltas;
|
|
cdpProfile.endTime = timeDeltas.reduce((x, y) => x + y, cdpProfile.startTime);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
export async function finalize(parseOptions = {}) {
|
|
parseCPUProfileData(parseOptions);
|
|
}
|
|
export function data() {
|
|
return {
|
|
profilesInProcess,
|
|
entryToNode,
|
|
};
|
|
}
|
|
function getOrCreatePreProcessedData(processId, profileId) {
|
|
const profileById = Platform.MapUtilities.getWithDefault(preprocessedData, processId, () => new Map());
|
|
return Platform.MapUtilities.getWithDefault(profileById, profileId, () => ({
|
|
rawProfile: {
|
|
startTime: 0,
|
|
endTime: 0,
|
|
nodes: [],
|
|
samples: [],
|
|
timeDeltas: [],
|
|
lines: [],
|
|
},
|
|
profileId,
|
|
}));
|
|
}
|
|
/**
|
|
* Returns the name of a function for a given synthetic profile call.
|
|
* We first look to find the ProfileNode representing this call, and use its
|
|
* function name. This is preferred (and should always exist) because if we
|
|
* resolve sourcemaps, we will update this name. If that name is not present,
|
|
* we fall back to the function name that was in the callframe that we got
|
|
* when parsing the profile's trace data.
|
|
*/
|
|
export function getProfileCallFunctionName(data, entry) {
|
|
const profile = data.profilesInProcess.get(entry.pid)?.get(entry.tid);
|
|
const node = profile?.parsedProfile.nodeById(entry.nodeId);
|
|
if (node?.functionName) {
|
|
return node.functionName;
|
|
}
|
|
return entry.callFrame.functionName;
|
|
}
|
|
//# sourceMappingURL=SamplesHandler.js.map |