Files
BrowserOS/reference-code/old-lib/utils/Profiler.ts
Felarof 8245dfe0ff Rewrite Agent Loop (#7)
* clean-up bunch of files for re-write

* more clean-up and adding basic agent

* Minor fix moved types into respective files.

* Deleted bunch of old files

backup

Update gitignore

Deleted a bunch of files

Remove message manager

Deleted old docs

Update rules

rename Profiler to profiler

* Temporarily adding old code

* Adding two small things back

* backup

* Implemented LangChainProvider and updated cursor rules

backup

LangChainProvider

curosr rules

* Implement tests for LangChainProvider -- unit test and integration test

integration test passes

integration test backup

* Tool Design

Tools Desing

tools design

* NavigationTool ready

NavigationTool ready

NavigationTool ready

NaivgationTool ready

backup

* MessageManager

MessageManager

backup

* Fixed integration test

* Agent design new

Updated agent design and added bunch of /NTN commands

agent new design

* Delete old agent design

* MessageManagerReadOnly class

* PlannerTool ready

PlannerTool almost ready

* ToolManager and DoneTool

* Integration of BrowserAgent

* BrowserAgent implementation v0.1

* BrowserAgent small fix v0.2

* Tool calling design

too call design

tool design claude

* Update agent tool design with // NTN

* add zod-to-json npm install

* BrowserAGent v0.3

* BrowserAgent v0.4

* BrowserAgent v0.5

* fixes

* Build error fixes in my NEWLY added code

build errors fix

* Build error fixes in old code (integration work)

backup

* Comment StreamEventProcessor for now, it is not used

* Small build error fix

* Small rename

* Added integration test to check structuredLLM and changed to 4o-mini

change default to nxtscape

integration test

* Small docstring

* Simplified BrowserAgent code and added integration test

Simplified BrowserAgent code

BrowserAGent integrationt est

* Update CLAUDE.md with project memory and instructions on how to write code

Update CLAUDE.md with project memory and instructions on how to write code

Project Memory

* Just a mova.. Moved ToolManager outside. Build works.

* TabOperations tool

TabOperations Tool and fixing some test

tab operations

* Update CLAUDE.md

* Added ClassificationTool

classifiction tool

classification prommpt

* Refactored and simplified PlannerTool unit test and integration test

* Updated Plnnaer tool

* Update CLAUDE.md

* BrowserAgent modified to do classification

BrowserAgent with classification

* minor fix to ToolManager

* Instead of ToolCall and ToolResult -- just updating message manager once

* minor fix to BrowserAgent integration test

* Changed done to "done_tool"

* Updated CLAUDE.md to reflect understanding of claude

* Uncommented stream event processor

* Renamed EventBus to StreamEventBus

* Commented StreamEventProcessor

* Event Processor

* Integrated EventProcessor with BrowserAgent

Added EventProcessor to BrowserAgetn

* Renamed StreamEventBus to EventBus

* Made EventBus required parameter in ExecutionContext

* PlanGenerator rewrite

PlanGenerator rewrite

backup

* For simple task, explicitly tell it to call done tool

* Max attempts for simple task

* backup

* Revert "backup"

This reverts commit 7d79a3d4d5774bfef79ec9827878b74edad3593f.

* Consolidating where EventBus and EventProcessor are created and initialized

backup

* Update CLAUDE.md

Update CLAUDE.md

* Improving agent loop code

Cleaned up processTooCall

classification task

* Create test-writer subAgent

test-agent-prompt

test agent prompt

test-agent-prompt

Update test-writer.md

* BrowserAgent test

Browseragent test

BrowserAgent test

* BrowserAgent refactor

backup

backup

* Minor fixes

* Minor fix

* minor change -- NEW AGENT LOOP IS WORKING WELL

* Update cursor rules

* Small change

* Improved BrowserAgent integration test

Improved BrowserAgent integration test

* Small change

* Update CLAUDE.md

* Different tools

* FindElementTool is ready

Find element update

backup

find element backup

* Updated to test strings to say "tests..."

* ScrollTool is ready

* RefreshStateTool is updated as well

* MessageManager updated

* SearchTool is ready

backup

* Interaction Element is also ready

* Add debugMessage emitter

* ValidatorTool ready and tests are passing

Validation Tool

validator tool

backup

backup

* GroupTabs tool ready

* Registered all the tools

* Planning changed to 5 steps

* BrowserAgent integration test fix

* Minor string changes

* backup

* Removed too many confusing events in EventProcessor -- there is only event.info right now

* Abort control implemented

backup

Abort

* Formatter for toolResult

Formatter for toolResult

backup

* Always render using Markdown

* Minor fix

---------

Co-authored-by: Nikhil Sonti <nikhilsv92@gmail.com>
2025-07-29 08:14:45 -07:00

539 lines
16 KiB
TypeScript

/**
* Simple performance profiler using performance.mark/measure
* Only active when DEV_MODE is enabled
*/
import { config } from '@/config';
// Control whether to write profile information to console
const ENABLE_CONSOLE_LOGGING = false;
// Track start times for calculating durations
const startTimes = new Map<string, number>();
// Call stack for tracking nested profiling
const callStack: string[] = [];
// Chrome Trace Event Format types
interface TraceEvent {
name: string; // Event name
cat: string; // Category (comma-separated list)
ph: 'B' | 'E' | 'X' | 'i' | 's' | 'f' | 'M'; // Phase: Begin/End/Complete/Instant/Async Start/Finish/Metadata
ts: number; // Timestamp in microseconds
pid: number; // Process ID
tid: number; // Thread ID
args?: Record<string, any>; // Additional arguments
dur?: number; // Duration for 'X' events
}
// Trace events collection
const traceEvents: TraceEvent[] = [];
// Store the initial timestamp for relative time calculation
let startTimestamp: number | null = null;
// Convert performance.now() to microseconds
function toMicroseconds(ms: number): number {
// Use relative timestamps starting from 0
if (startTimestamp === null) {
startTimestamp = ms;
}
// Return relative microseconds from start
return Math.round((ms - startTimestamp) * 1000);
}
/**
* Start a profile if DEV_MODE is enabled
* @param label - Profile label to identify in DevTools
*/
export function profileStart(label: string): void {
if (!config.DEV_MODE) return;
try {
const startTime = performance.now();
startTimes.set(label, startTime);
// Add to call stack
callStack.push(label);
// Create a performance mark
performance.mark(`${label}-start`);
// Add Begin trace event
traceEvents.push({
name: label,
cat: 'profile',
ph: 'B',
ts: toMicroseconds(startTime),
pid: 1,
tid: 1
});
// Log start in console with indentation based on call stack depth
if (ENABLE_CONSOLE_LOGGING) {
const indent = ' '.repeat(callStack.length - 1);
console.log(`${indent}⏱️ [START] ${label}`);
}
} catch (e) {
// Silently fail if performance API not available
}
}
/**
* End a profile if DEV_MODE is enabled
* @param label - Profile label to end
*/
export function profileEnd(label: string): void {
if (!config.DEV_MODE) return;
try {
const startTime = startTimes.get(label);
if (startTime !== undefined) {
const endTime = performance.now();
const duration = endTime - startTime;
startTimes.delete(label);
// Remove from call stack
const stackIndex = callStack.lastIndexOf(label);
if (stackIndex !== -1) {
callStack.splice(stackIndex, 1);
}
// Create end mark and measure
performance.mark(`${label}-end`);
performance.measure(label, `${label}-start`, `${label}-end`);
// Add End trace event
traceEvents.push({
name: label,
cat: 'profile',
ph: 'E',
ts: toMicroseconds(endTime),
pid: 1,
tid: 1
});
// Log with color coding based on duration and indentation
if (ENABLE_CONSOLE_LOGGING) {
const indent = ' '.repeat(callStack.length);
const color = duration > 1000 ? '🔴' : duration > 500 ? '🟡' : '🟢';
console.log(`${indent}${color} [END] ${label}: ${duration.toFixed(2)}ms`);
}
// Clean up marks
performance.clearMarks(`${label}-start`);
performance.clearMarks(`${label}-end`);
}
} catch (e) {
// Silently fail if performance API not available
}
}
/**
* Profile an async function
* @param label - Profile label
* @param fn - Async function to profile
* @returns Result of the function
*/
export async function profileAsync<T>(
label: string,
fn: () => Promise<T>
): Promise<T> {
profileStart(label);
try {
return await fn();
} finally {
profileEnd(label);
}
}
/**
* Profile a sync function
* @param label - Profile label
* @param fn - Function to profile
* @returns Result of the function
*/
export function profileSync<T>(
label: string,
fn: () => T
): T {
profileStart(label);
try {
return fn();
} finally {
profileEnd(label);
}
}
/**
* Method decorator for profiling
* @param customLabel - Optional custom label (defaults to ClassName.methodName)
*/
export function profile(customLabel?: string) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
const label = customLabel || `${target.constructor.name}.${propertyKey}`;
descriptor.value = function (...args: any[]) {
const result = originalMethod.apply(this, args);
// Handle async methods
if (result && typeof result.then === 'function') {
return profileAsync(label, () => result);
}
// Handle sync methods
return profileSync(label, () => result);
};
return descriptor;
};
}
/**
* Get all performance measures (for debugging)
* @returns Array of performance entries
*/
export function getProfileMeasures(): PerformanceEntry[] {
if (!config.DEV_MODE) return [];
try {
return performance.getEntriesByType('measure');
} catch (e) {
return [];
}
}
/**
* Display performance report in console
*/
export function showProfileReport(): void {
if (!config.DEV_MODE || !ENABLE_CONSOLE_LOGGING) return;
const measures = getProfileMeasures();
if (measures.length === 0) {
console.log('No performance measures recorded');
return;
}
console.log('\n📊 Performance Report (All Measures):');
console.log('='.repeat(70));
// Group measures by name to show counts
const operationCounts: Record<string, number> = {};
measures.forEach(measure => {
operationCounts[measure.name] = (operationCounts[measure.name] || 0) + 1;
});
const sortedMeasures = [...measures].sort((a, b) => b.duration - a.duration);
sortedMeasures.forEach(measure => {
const duration = measure.duration;
const color = duration > 1000 ? '🔴' : duration > 500 ? '🟡' : '🟢';
const count = operationCounts[measure.name];
const countInfo = count > 1 ? ` (1 of ${count})` : '';
console.log(`${color} ${measure.name.padEnd(40)} ${duration.toFixed(2).padStart(10)}ms${countInfo}`);
});
console.log('='.repeat(70));
console.log(`Total measures: ${measures.length}`);
console.log(`Unique operations: ${Object.keys(operationCounts).length}`);
}
/**
* Get performance summary with total time and top operations
* @returns Object containing total time and top operations
*/
export function getPerformanceSummary(): {
totalTime: number;
totalCount: number;
avgTime: number;
topOperations: Array<{ name: string; duration: number; count: number; avgDuration: number }>;
frequentOperations: Array<{ name: string; count: number; totalDuration: number; avgDuration: number }>;
} {
if (!config.DEV_MODE) return { totalTime: 0, totalCount: 0, avgTime: 0, topOperations: [], frequentOperations: [] };
const measures = getProfileMeasures();
if (measures.length === 0) {
return { totalTime: 0, totalCount: 0, avgTime: 0, topOperations: [], frequentOperations: [] };
}
let totalTime = 0;
const operationStats: Record<string, { count: number; totalDuration: number; maxDuration: number }> = {};
// Process each measure and group by operation name
measures.forEach(measure => {
totalTime += measure.duration;
if (!operationStats[measure.name]) {
operationStats[measure.name] = { count: 0, totalDuration: 0, maxDuration: 0 };
}
operationStats[measure.name].count++;
operationStats[measure.name].totalDuration += measure.duration;
operationStats[measure.name].maxDuration = Math.max(operationStats[measure.name].maxDuration, measure.duration);
});
// Calculate average
const avgTime = totalTime / measures.length;
// Convert to array and calculate averages
const operationsArray = Object.entries(operationStats).map(([name, stats]) => ({
name,
count: stats.count,
totalDuration: stats.totalDuration,
avgDuration: stats.totalDuration / stats.count,
maxDuration: stats.maxDuration
}));
// Get top 5 operations by max duration
const topOperations = [...operationsArray]
.sort((a, b) => b.maxDuration - a.maxDuration)
.slice(0, 5)
.map(op => ({
name: op.name,
duration: op.maxDuration,
count: op.count,
avgDuration: op.avgDuration
}));
// Get top 5 most frequent operations
const frequentOperations = [...operationsArray]
.sort((a, b) => b.count - a.count)
.slice(0, 5)
.map(op => ({
name: op.name,
count: op.count,
totalDuration: op.totalDuration,
avgDuration: op.avgDuration
}));
return { totalTime, totalCount: measures.length, avgTime, topOperations, frequentOperations };
}
/**
* Display a compact performance summary
*/
export function showPerformanceSummary(): void {
if (!config.DEV_MODE || !ENABLE_CONSOLE_LOGGING) return;
const summary = getPerformanceSummary();
if (summary.totalTime === 0) {
console.log('No performance data to summarize');
return;
}
console.log('\n📈 Performance Summary:');
console.log('='.repeat(50));
console.log(`⏱️ Total Time: ${summary.totalTime.toFixed(2)}ms`);
console.log(`📊 Total Operations: ${summary.totalCount}`);
console.log(`📉 Average Time: ${summary.avgTime.toFixed(2)}ms`);
console.log('\n🔥 Slowest Operations (by max duration):');
summary.topOperations.forEach((op, index) => {
const color = op.duration > 1000 ? '🔴' : op.duration > 500 ? '🟡' : '🟢';
console.log(`${color} ${(index + 1).toString().padEnd(2)}. ${op.name.padEnd(40)} Max: ${op.duration.toFixed(2).padStart(10)}ms | Count: ${op.count.toString().padStart(3)} | Avg: ${op.avgDuration.toFixed(2).padStart(10)}ms`);
});
console.log('\n🔄 Most Frequent Operations:');
summary.frequentOperations.forEach((op, index) => {
const color = op.avgDuration > 1000 ? '🔴' : op.avgDuration > 500 ? '🟡' : '🟢';
console.log(`${color} ${(index + 1).toString().padEnd(2)}. ${op.name.padEnd(40)} Count: ${op.count.toString().padStart(3)} | Total: ${op.totalDuration.toFixed(2).padStart(10)}ms | Avg: ${op.avgDuration.toFixed(2).padStart(10)}ms`);
});
console.log('='.repeat(50));
}
/**
* Export trace events in Chrome Trace Event Format
* @returns JSON string that can be loaded in chrome://tracing or Perfetto
*/
export function exportTraceEvents(): string {
if (!config.DEV_MODE) return '{"traceEvents":[]}';
// Add metadata events for Chrome trace format
const metadataEvents: TraceEvent[] = [
{
name: 'process_name',
ph: 'M',
ts: 0,
pid: 1,
tid: 1,
cat: '__metadata',
args: {
name: 'Browser Extension'
}
},
{
name: 'thread_name',
ph: 'M',
ts: 0,
pid: 1,
tid: 1,
cat: '__metadata',
args: {
name: 'Main Thread'
}
}
];
// Combine metadata and trace events
const allEvents = [...metadataEvents, ...traceEvents];
// Chrome JSON trace format expects either:
// 1. An array of events (JSON Array Format)
// 2. An object with "traceEvents" array (JSON Object Format)
// Using JSON Object Format for better compatibility
const trace = {
traceEvents: allEvents
};
// Optional: Add displayTimeUnit if you want to override default
// trace.displayTimeUnit = "ms";
return JSON.stringify(trace);
}
/**
* Export trace events in legacy Chrome trace array format
* @returns JSON string with just an array of events
*/
export function exportTraceEventsLegacy(): string {
if (!config.DEV_MODE) return '[]';
// Add metadata events for Chrome trace format
const metadataEvents: TraceEvent[] = [
{
name: 'process_name',
ph: 'M',
ts: 0,
pid: 1,
tid: 1,
cat: '__metadata',
args: {
name: 'Browser Extension'
}
},
{
name: 'thread_name',
ph: 'M',
ts: 0,
pid: 1,
tid: 1,
cat: '__metadata',
args: {
name: 'Main Thread'
}
}
];
// Combine metadata and trace events
const allEvents = [...metadataEvents, ...traceEvents];
// Return just the array (JSON Array Format)
return JSON.stringify(allEvents);
}
/**
* Clear all trace events
*/
export function clearTraceEvents(): void {
traceEvents.length = 0;
callStack.length = 0;
startTimestamp = null;
startTimes.clear();
if (ENABLE_CONSOLE_LOGGING) {
console.log('🧹 Trace events cleared');
}
}
/**
* Get current trace event count
*/
export function getTraceEventCount(): number {
return traceEvents.length;
}
/**
* Generate a test trace to verify format
*/
export function generateTestTrace(): void {
if (!config.DEV_MODE) return;
// Clear existing traces
clearTraceEvents();
if (ENABLE_CONSOLE_LOGGING) {
console.log('🧪 Generating test trace...');
}
// Simulate nested method calls
profileStart('main');
profileStart('processData');
profileStart('fetchData');
profileEnd('fetchData');
profileStart('parseData');
profileEnd('parseData');
profileEnd('processData');
profileStart('renderResults');
profileEnd('renderResults');
profileEnd('main');
if (ENABLE_CONSOLE_LOGGING) {
console.log(`✅ Test trace generated with ${getTraceEventCount()} events`);
console.log('💡 Use profiler.exportTrace() to get the trace data');
}
}
// Make functions available globally for debugging
if (config.DEV_MODE) {
// Use globalThis for better compatibility across environments (window, service workers, etc.)
(globalThis as any).__profileReport = showProfileReport;
(globalThis as any).__profileMeasures = getProfileMeasures;
(globalThis as any).__profileStart = profileStart;
(globalThis as any).__profileEnd = profileEnd;
(globalThis as any).__profileSummary = showPerformanceSummary;
(globalThis as any).__profileGetSummary = getPerformanceSummary;
(globalThis as any).__profileExportTrace = exportTraceEvents;
(globalThis as any).__profileExportTraceLegacy = exportTraceEventsLegacy;
(globalThis as any).__profileClearTrace = clearTraceEvents;
(globalThis as any).__profileTraceCount = getTraceEventCount;
(globalThis as any).__profileTestTrace = generateTestTrace;
// Also expose under a namespace for cleaner access
(globalThis as any).profiler = {
start: profileStart,
end: profileEnd,
report: showProfileReport,
measures: getProfileMeasures,
summary: showPerformanceSummary,
getSummary: getPerformanceSummary,
exportTrace: exportTraceEvents,
exportTraceLegacy: exportTraceEventsLegacy,
clearTrace: clearTraceEvents,
traceCount: getTraceEventCount,
testTrace: generateTestTrace,
};
if (ENABLE_CONSOLE_LOGGING) {
console.log('🚀 Profiler with trace export loaded. Key commands:');
console.log(' profiler.start(label) - Start profiling');
console.log(' profiler.end(label) - End profiling');
console.log(' profiler.exportTrace() - Export trace for Perfetto/chrome://tracing');
console.log(' profiler.exportTraceLegacy() - Export trace in array format');
console.log(' profiler.clearTrace() - Clear all trace events');
console.log(' profiler.testTrace() - Generate test trace');
console.log(' profiler.report() - Show performance report');
console.log(' profiler.summary() - Show performance summary');
console.log(' ');
console.log(' 💡 Usage: copy(profiler.exportTrace()) then save as .json');
}
}