or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-web-features.mdbrowser-page-control.mdcore-debugging.mddevice-testing.mddom-styling.mdindex.mdnetwork-performance.mdstorage-data.md
tile.json

network-performance.mddocs/

Network and Performance

Network request/response analysis, performance monitoring, and resource optimization. This covers domains that provide comprehensive insights into network traffic, resource loading, and performance metrics.

Capabilities

Network Domain

Network request/response interception and analysis for monitoring all network activity.

namespace Protocol.Network {
  type RequestId = string;
  type LoaderId = string;
  type MonotonicTime = number;
  type TimeSinceEpoch = number;
  type InterceptionId = string;
  type ErrorReason = ('Failed' | 'Aborted' | 'TimedOut' | 'AccessDenied' | 'ConnectionClosed' | 'ConnectionReset' | 'ConnectionRefused' | 'ConnectionAborted' | 'ConnectionFailed' | 'NameNotResolved' | 'InternetDisconnected' | 'AddressUnreachable' | 'BlockedByClient' | 'BlockedByResponse');
  type ResourceType = ('Document' | 'Stylesheet' | 'Image' | 'Media' | 'Font' | 'Script' | 'TextTrack' | 'XHR' | 'Fetch' | 'Prefetch' | 'EventSource' | 'WebSocket' | 'Manifest' | 'SignedExchange' | 'Ping' | 'CSPViolationReport' | 'Preflight' | 'Other');

  interface Request {
    url: string;
    urlFragment?: string;
    method: string;
    headers: Headers;
    postData?: string;
    hasPostData?: boolean;
    postDataEntries?: PostDataEntry[];
    mixedContentType?: MixedContentType;
    initialPriority: ResourcePriority;
    referrerPolicy: ReferrerPolicy;
    isLinkPreload?: boolean;
    trustTokenParams?: TrustTokenParams;
    isSameOrigin?: boolean;
  }

  interface Response {
    url: string;
    status: integer;
    statusText: string;
    headers: Headers;
    headersText?: string;
    mimeType: string;
    charset?: string;
    requestHeaders?: Headers;
    requestHeadersText?: string;
    connectionReused: boolean;
    connectionId: number;
    remoteIPAddress?: string;
    remotePort?: integer;
    fromDiskCache?: boolean;
    fromServiceWorker?: boolean;
    fromPrefetchCache?: boolean;
    encodedDataLength: number;
    timing?: ResourceTiming;
    serviceWorkerResponseSource?: ServiceWorkerResponseSource;
    responseTime?: TimeSinceEpoch;
    cacheStorageCacheName?: string;
    protocol?: string;
    alternateProtocolUsage?: AlternateProtocolUsage;
    securityState: Security.SecurityState;
    securityDetails?: SecurityDetails;
  }

  interface ResourceTiming {
    requestTime: number;
    proxyStart: number;
    proxyEnd: number;
    dnsStart: number;
    dnsEnd: number;
    connectStart: number;
    connectEnd: number;
    sslStart: number;
    sslEnd: number;
    workerStart: number;
    workerReady: number;
    workerFetchStart: number;
    workerRespondWithSettled: number;
    sendStart: number;
    sendEnd: number;
    pushStart: number;
    pushEnd: number;
    receiveHeadersStart: number;
    receiveHeadersEnd: number;
  }

  interface RequestWillBeSentEvent {
    requestId: RequestId;
    loaderId: LoaderId;
    documentURL: string;
    request: Request;
    timestamp: MonotonicTime;
    wallTime: TimeSinceEpoch;
    initiator: Initiator;
    redirectHasExtraInfo?: boolean;
    type?: ResourceType;
    frameId?: Page.FrameId;
    hasUserGesture?: boolean;
  }

  interface ResponseReceivedEvent {
    requestId: RequestId;
    loaderId: LoaderId;
    timestamp: MonotonicTime;
    type: ResourceType;
    response: Response;
    hasExtraInfo?: boolean;
    frameId?: Page.FrameId;
  }

  interface LoadingFinishedEvent {
    requestId: RequestId;
    timestamp: MonotonicTime;
    encodedDataLength: number;
    shouldReportCorbBlocking?: boolean;
  }

  interface LoadingFailedEvent {
    requestId: RequestId;
    timestamp: MonotonicTime;
    type: ResourceType;
    errorText: string;
    canceled?: boolean;
    blockedReason?: BlockedReason;
    corsErrorStatus?: CorsErrorStatus;
  }

  interface GetResponseBodyRequest {
    requestId: RequestId;
  }

  interface GetResponseBodyResponse {
    body: string;
    base64Encoded: boolean;
  }

  interface SetRequestInterceptionRequest {
    patterns: RequestPattern[];
  }

  interface RequestPattern {
    urlPattern?: string;
    resourceType?: ResourceType;
    interceptionStage?: InterceptionStage;
  }

  interface ContinueInterceptedRequestRequest {
    interceptionId: InterceptionId;
    errorReason?: ErrorReason;
    rawResponse?: string;
    url?: string;
    method?: string;
    postData?: string;
    headers?: Headers;
    authChallengeResponse?: AuthChallengeResponse;
  }
}

Usage Example:

import Protocol from "devtools-protocol/types/protocol";

class NetworkMonitor {
  private pendingRequests: Map<string, Protocol.Network.Request> = new Map();
  private completedRequests: Map<string, NetworkRequestInfo> = new Map();

  setupNetworkListeners(): void {
    this.onRequestWillBeSent = (event: Protocol.Network.RequestWillBeSentEvent) => {
      console.log(`Request: ${event.request.method} ${event.request.url}`);
      this.pendingRequests.set(event.requestId, event.request);
    };

    this.onResponseReceived = (event: Protocol.Network.ResponseReceivedEvent) => {
      console.log(`Response: ${event.response.status} ${event.response.url}`);
    };

    this.onLoadingFinished = (event: Protocol.Network.LoadingFinishedEvent) => {
      const request = this.pendingRequests.get(event.requestId);
      if (request) {
        this.completedRequests.set(event.requestId, {
          request,
          encodedDataLength: event.encodedDataLength,
          timestamp: event.timestamp
        });
        this.pendingRequests.delete(event.requestId);
      }
    };

    this.onLoadingFailed = (event: Protocol.Network.LoadingFailedEvent) => {
      console.error(`Request failed: ${event.errorText} for ${event.requestId}`);
      this.pendingRequests.delete(event.requestId);
    };
  }

  async getResponseBody(requestId: string): Promise<{ body: string; base64Encoded: boolean }> {
    const request: Protocol.Network.GetResponseBodyRequest = { requestId };
    
    // Implementation would send Network.getResponseBody
    return {
      body: "Response content",
      base64Encoded: false
    };
  }

  async setupRequestInterception(patterns: Protocol.Network.RequestPattern[]): Promise<void> {
    const request: Protocol.Network.SetRequestInterceptionRequest = { patterns };
    
    // Implementation would send Network.setRequestInterception
  }

  analyzeNetworkPerformance(): NetworkAnalysis {
    const requests = Array.from(this.completedRequests.values());
    
    return {
      totalRequests: requests.length,
      totalBytes: requests.reduce((sum, req) => sum + req.encodedDataLength, 0),
      averageResponseTime: this.calculateAverageResponseTime(requests),
      resourceTypes: this.groupByResourceType(requests)
    };
  }

  private calculateAverageResponseTime(requests: NetworkRequestInfo[]): number {
    // Implementation would calculate based on timing data
    return 0;
  }

  private groupByResourceType(requests: NetworkRequestInfo[]): Record<string, number> {
    // Implementation would group requests by type
    return {};
  }
}

interface NetworkRequestInfo {
  request: Protocol.Network.Request;
  encodedDataLength: number;
  timestamp: Protocol.Network.MonotonicTime;
}

interface NetworkAnalysis {
  totalRequests: number;
  totalBytes: number;
  averageResponseTime: number;
  resourceTypes: Record<string, number>;
}

Performance Domain

Performance timeline and metrics collection for analyzing runtime performance.

namespace Protocol.Performance {
  interface Metric {
    name: string;
    value: number;
  }

  interface GetMetricsResponse {
    metrics: Metric[];
  }

  interface SetTimeDomainRequest {
    timeDomain: ('timeTicks' | 'threadTicks');
  }

  interface EnableRequest {
    timeDomain?: ('timeTicks' | 'threadTicks');
  }

  interface MetricsEvent {
    metrics: Metric[];
    title: string;
  }
}

PerformanceTimeline Domain

Performance timeline API for detailed performance measurement.

namespace Protocol.PerformanceTimeline {
  interface LargestContentfulPaint {
    renderTime?: Network.TimeSinceEpoch;
    loadTime?: Network.TimeSinceEpoch;
    size?: number;
    elementId?: string;
    url?: string;
    nodeId?: DOM.NodeId;
  }

  interface LayoutShiftAttribution {
    previousRect: DOM.Rect;
    currentRect: DOM.Rect;
    nodeId?: DOM.NodeId;
  }

  interface LayoutShift {
    value: number;
    hadRecentInput: boolean;
    lastInputTime: Network.TimeSinceEpoch;
    sources: LayoutShiftAttribution[];
  }

  interface TimelineEvent {
    frameId: Page.FrameId;
    type: string;
    name: string;
    time: Network.TimeSinceEpoch;
    duration?: number;
    lcpDetails?: LargestContentfulPaint;
    layoutShiftDetails?: LayoutShift;
  }

  interface EnableRequest {
    eventTypes: string[];
  }

  interface TimelineEventAddedEvent {
    event: TimelineEvent;
  }
}

Memory Domain

Memory usage monitoring and analysis for performance optimization.

namespace Protocol.Memory {
  interface GetDOMCountersResponse {
    documents: integer;
    nodes: integer;
    jsEventListeners: integer;
  }

  interface PrepareForLeakDetectionResponse {}

  interface GetAllTimeSamplingProfileResponse {
    profile: SamplingProfile;
  }

  interface GetBrowserSamplingProfileResponse {
    profile: SamplingProfile;
  }

  interface GetSamplingProfileResponse {
    profile: SamplingProfile;
  }

  interface SamplingProfile {
    samples: SamplingProfileNode[];
    modules: Module[];
  }

  interface SamplingProfileNode {
    size: number;
    total: number;
    stack: string[];
  }

  interface Module {
    name: string;
    uuid: string;
    baseAddress: string;
    size: number;
  }
}

Tracing Domain

Performance tracing and timeline recording for detailed analysis.

namespace Protocol.Tracing {
  interface TraceConfig {
    recordMode?: ('recordUntilFull' | 'recordContinuously' | 'recordAsMuchAsPossible' | 'echoToConsole');
    traceBufferSizeInKb?: number;
    enableSampling?: boolean;
    enableSystrace?: boolean;
    enableArgumentFilter?: boolean;
    includedCategories?: string[];
    excludedCategories?: string[];
    syntheticDelays?: string[];
    memoryDumpConfig?: MemoryDumpConfig;
  }

  interface MemoryDumpConfig {
    [key: string]: any;
  }

  interface StartRequest {
    categories?: string;
    options?: string;
    bufferUsageReportingInterval?: number;
    transferMode?: ('ReportEvents' | 'ReturnAsStream');
    streamFormat?: ('json' | 'proto');
    streamCompression?: ('none' | 'gzip');
    traceConfig?: TraceConfig;
    perfettoConfig?: string;
    tracingBackend?: ('auto' | 'chrome' | 'system');
  }

  interface EndRequest {
    stream?: IO.StreamHandle;
  }

  interface GetCategoriesResponse {
    categories: string[];
  }

  interface RequestMemoryDumpRequest {
    deterministic?: boolean;
    levelOfDetail?: ('background' | 'light' | 'detailed');
  }

  interface RequestMemoryDumpResponse {
    dumpGuid: string;
    success: boolean;
  }

  interface BufferUsageEvent {
    percentFull?: number;
    eventCount?: integer;
    value?: number;
  }

  interface DataCollectedEvent {
    value: any[];
  }

  interface TracingCompleteEvent {
    dataLossOccurred: boolean;
    stream?: IO.StreamHandle;
    traceFormat?: ('json' | 'proto');
    streamCompression?: ('none' | 'gzip');
  }
}

Usage Example:

import Protocol from "devtools-protocol/types/protocol";

class PerformanceProfiler {
  async startPerformanceMonitoring(): Promise<void> {
    // Enable performance domain
    const perfRequest: Protocol.Performance.EnableRequest = {
      timeDomain: 'timeTicks'
    };
    
    // Enable performance timeline
    const timelineRequest: Protocol.PerformanceTimeline.EnableRequest = {
      eventTypes: ['largest-contentful-paint', 'layout-shift', 'first-input']
    };
    
    // Start tracing
    const tracingRequest: Protocol.Tracing.StartRequest = {
      categories: "devtools.timeline,v8.execute,blink.console,blink.user_timing",
      transferMode: 'ReportEvents',
      traceConfig: {
        recordMode: 'recordUntilFull',
        includedCategories: ['devtools.timeline', 'v8.execute']
      }
    };
    
    // Implementation would send respective enable/start commands
  }

  async getPerformanceMetrics(): Promise<Protocol.Performance.Metric[]> {
    // Implementation would send Performance.getMetrics
    return [
      { name: "Timestamp", value: Date.now() },
      { name: "Documents", value: 1 },
      { name: "Nodes", value: 150 },
      { name: "JSEventListeners", value: 25 },
      { name: "LayoutCount", value: 12 },
      { name: "RecalcStyleCount", value: 8 }
    ];
  }

  setupPerformanceListeners(): void {
    this.onTimelineEvent = (event: Protocol.PerformanceTimeline.TimelineEventAddedEvent) => {
      const { event: timelineEvent } = event;
      
      switch (timelineEvent.type) {
        case 'largest-contentful-paint':
          console.log("LCP event:", timelineEvent.lcpDetails);
          break;
        case 'layout-shift':
          console.log("Layout shift:", timelineEvent.layoutShiftDetails);
          break;
        default:
          console.log("Timeline event:", timelineEvent.name);
      }
    };

    this.onTracingDataCollected = (event: Protocol.Tracing.DataCollectedEvent) => {
      // Process tracing data
      console.log("Tracing data collected:", event.value.length, "events");
    };

    this.onTracingComplete = (event: Protocol.Tracing.TracingCompleteEvent) => {
      console.log("Tracing complete, data loss:", event.dataLossOccurred);
    };
  }

  async measureUserTimings(): Promise<UserTiming[]> {
    // Implementation would collect performance.mark() and performance.measure() data
    return [
      { name: "custom-mark-1", startTime: 1000, duration: 0, entryType: "mark" },
      { name: "custom-measure-1", startTime: 1000, duration: 50, entryType: "measure" }
    ];
  }

  async analyzeMemoryUsage(): Promise<MemoryAnalysis> {
    // Get DOM counters
    // Implementation would send Memory.getDOMCounters
    const domCounters = {
      documents: 1,
      nodes: 150,
      jsEventListeners: 25
    };

    // Get sampling profile
    // Implementation would send Memory.getSamplingProfile
    const samplingProfile: Protocol.Memory.SamplingProfile = {
      samples: [],
      modules: []
    };

    return {
      domCounters,
      samplingProfile,
      recommendations: this.generateMemoryRecommendations(domCounters)
    };
  }

  private generateMemoryRecommendations(counters: { documents: number; nodes: number; jsEventListeners: number }): string[] {
    const recommendations: string[] = [];
    
    if (counters.nodes > 1000) {
      recommendations.push("Consider reducing DOM complexity - high node count detected");
    }
    
    if (counters.jsEventListeners > 100) {
      recommendations.push("High number of event listeners - consider event delegation");
    }
    
    return recommendations;
  }
}

interface UserTiming {
  name: string;
  startTime: number;
  duration: number;
  entryType: string;
}

interface MemoryAnalysis {
  domCounters: { documents: number; nodes: number; jsEventListeners: number };
  samplingProfile: Protocol.Memory.SamplingProfile;
  recommendations: string[];
}

Common Usage Patterns

Network Traffic Analysis

import Protocol from "devtools-protocol/types/protocol";

class NetworkAnalyzer {
  private requests: Map<string, NetworkRequestData> = new Map();

  analyzeNetworkTraffic(): NetworkInsights {
    const allRequests = Array.from(this.requests.values());
    
    return {
      totalRequests: allRequests.length,
      totalBytes: this.calculateTotalBytes(allRequests),
      slowRequests: this.findSlowRequests(allRequests),
      failedRequests: this.findFailedRequests(allRequests),
      resourceBreakdown: this.analyzeResourceTypes(allRequests),
      cacheAnalysis: this.analyzeCacheUsage(allRequests),
      securityIssues: this.findSecurityIssues(allRequests)
    };
  }

  private calculateTotalBytes(requests: NetworkRequestData[]): { sent: number; received: number } {
    return requests.reduce(
      (totals, req) => ({
        sent: totals.sent + (req.request.postData?.length || 0),
        received: totals.received + (req.response?.encodedDataLength || 0)
      }),
      { sent: 0, received: 0 }
    );
  }

  private findSlowRequests(requests: NetworkRequestData[], threshold: number = 1000): NetworkRequestData[] {
    return requests.filter(req => {
      const timing = req.response?.timing;
      if (!timing) return false;
      
      const totalTime = timing.receiveHeadersEnd - timing.requestTime;
      return totalTime > threshold;
    });
  }

  private findFailedRequests(requests: NetworkRequestData[]): NetworkRequestData[] {
    return requests.filter(req => req.failed || (req.response && req.response.status >= 400));
  }

  private analyzeResourceTypes(requests: NetworkRequestData[]): Record<Protocol.Network.ResourceType, number> {
    const breakdown: Record<string, number> = {};
    
    requests.forEach(req => {
      const type = req.resourceType || 'Other';
      breakdown[type] = (breakdown[type] || 0) + 1;
    });
    
    return breakdown as Record<Protocol.Network.ResourceType, number>;
  }

  private analyzeCacheUsage(requests: NetworkRequestData[]): CacheAnalysis {
    let fromCache = 0;
    let fromServiceWorker = 0;
    let fromPrefetchCache = 0;
    
    requests.forEach(req => {
      const response = req.response;
      if (response?.fromDiskCache) fromCache++;
      if (response?.fromServiceWorker) fromServiceWorker++;
      if (response?.fromPrefetchCache) fromPrefetchCache++;
    });
    
    return {
      fromCache,
      fromServiceWorker,
      fromPrefetchCache,
      cacheHitRate: fromCache / requests.length
    };
  }

  private findSecurityIssues(requests: NetworkRequestData[]): SecurityIssue[] {
    const issues: SecurityIssue[] = [];
    
    requests.forEach(req => {
      // Check for mixed content
      if (req.request.url.startsWith('http:') && req.documentURL?.startsWith('https:')) {
        issues.push({
          type: 'mixed-content',
          url: req.request.url,
          description: 'HTTP resource loaded from HTTPS page'
        });
      }
      
      // Check for weak security state
      if (req.response?.securityState === 'insecure') {
        issues.push({
          type: 'insecure-connection',
          url: req.request.url,
          description: 'Insecure connection detected'
        });
      }
    });
    
    return issues;
  }
}

interface NetworkRequestData {
  requestId: string;
  request: Protocol.Network.Request;
  response?: Protocol.Network.Response;
  resourceType?: Protocol.Network.ResourceType;
  failed?: boolean;
  errorText?: string;
  documentURL?: string;
}

interface NetworkInsights {
  totalRequests: number;
  totalBytes: { sent: number; received: number };
  slowRequests: NetworkRequestData[];
  failedRequests: NetworkRequestData[];
  resourceBreakdown: Record<Protocol.Network.ResourceType, number>;
  cacheAnalysis: CacheAnalysis;
  securityIssues: SecurityIssue[];
}

interface CacheAnalysis {
  fromCache: number;
  fromServiceWorker: number;
  fromPrefetchCache: number;
  cacheHitRate: number;
}

interface SecurityIssue {
  type: string;
  url: string;
  description: string;
}

Performance Monitoring Dashboard

import Protocol from "devtools-protocol/types/protocol";

class PerformanceDashboard {
  private metrics: Protocol.Performance.Metric[] = [];
  private timelineEvents: Protocol.PerformanceTimeline.TimelineEvent[] = [];
  private tracingData: any[] = [];

  async generatePerformanceReport(): Promise<PerformanceReport> {
    const coreWebVitals = this.calculateCoreWebVitals();
    const resourceMetrics = this.analyzeResourcePerformance();
    const runtimeMetrics = this.analyzeRuntimePerformance();
    const memoryAnalysis = await this.analyzeMemoryUsage();
    
    return {
      coreWebVitals,
      resourceMetrics,
      runtimeMetrics,
      memoryAnalysis,
      recommendations: this.generateRecommendations(coreWebVitals, resourceMetrics, runtimeMetrics)
    };
  }

  private calculateCoreWebVitals(): CoreWebVitals {
    let lcp: number | null = null;
    let fid: number | null = null;
    let cls: number = 0;

    this.timelineEvents.forEach(event => {
      switch (event.type) {
        case 'largest-contentful-paint':
          if (event.lcpDetails?.renderTime) {
            lcp = event.lcpDetails.renderTime;
          }
          break;
        case 'first-input':
          if (event.duration) {
            fid = event.duration;
          }
          break;
        case 'layout-shift':
          if (event.layoutShiftDetails) {
            cls += event.layoutShiftDetails.value;
          }
          break;
      }
    });

    return { lcp, fid, cls };
  }

  private analyzeResourcePerformance(): ResourceMetrics {
    // Analyze resource loading from tracing data
    const resourceEvents = this.tracingData.filter(event => 
      event.cat === 'loading' || event.cat === 'devtools.timeline'
    );

    return {
      totalLoadTime: this.calculateTotalLoadTime(resourceEvents),
      criticalResourceCount: this.countCriticalResources(resourceEvents),
      renderBlockingResources: this.findRenderBlockingResources(resourceEvents)
    };
  }

  private analyzeRuntimePerformance(): RuntimeMetrics {
    const jsExecutionTime = this.metrics.find(m => m.name === 'ScriptDuration')?.value || 0;
    const layoutCount = this.metrics.find(m => m.name === 'LayoutCount')?.value || 0;
    const styleRecalcCount = this.metrics.find(m => m.name === 'RecalcStyleCount')?.value || 0;

    return {
      jsExecutionTime,
      layoutCount,
      styleRecalcCount,
      mainThreadBlockingTime: this.calculateMainThreadBlockingTime()
    };
  }

  private async analyzeMemoryUsage(): Promise<MemoryMetrics> {
    // Get memory metrics
    const domCounters = {
      documents: this.metrics.find(m => m.name === 'Documents')?.value || 0,
      nodes: this.metrics.find(m => m.name === 'Nodes')?.value || 0,
      jsEventListeners: this.metrics.find(m => m.name === 'JSEventListeners')?.value || 0
    };

    return {
      domCounters,
      heapSize: this.metrics.find(m => m.name === 'JSHeapUsedSize')?.value || 0,
      memoryLeaks: this.detectPotentialMemoryLeaks(domCounters)
    };
  }

  private generateRecommendations(
    vitals: CoreWebVitals,
    resources: ResourceMetrics,
    runtime: RuntimeMetrics
  ): string[] {
    const recommendations: string[] = [];

    if (vitals.lcp && vitals.lcp > 2500) {
      recommendations.push("Improve Largest Contentful Paint - optimize critical resources and images");
    }

    if (vitals.fid && vitals.fid > 100) {
      recommendations.push("Reduce First Input Delay - minimize JavaScript execution time");
    }

    if (vitals.cls > 0.1) {
      recommendations.push("Improve Cumulative Layout Shift - reserve space for dynamic content");
    }

    if (runtime.jsExecutionTime > 1000) {
      recommendations.push("Optimize JavaScript execution - consider code splitting and lazy loading");
    }

    if (resources.renderBlockingResources > 10) {
      recommendations.push("Reduce render-blocking resources - minimize CSS and defer non-critical JS");
    }

    return recommendations;
  }

  private calculateTotalLoadTime(events: any[]): number {
    // Implementation would analyze loading events
    return 0;
  }

  private countCriticalResources(events: any[]): number {
    // Implementation would count critical path resources
    return 0;
  }

  private findRenderBlockingResources(events: any[]): number {
    // Implementation would identify render-blocking resources
    return 0;
  }

  private calculateMainThreadBlockingTime(): number {
    // Implementation would calculate blocking time from tracing data
    return 0;
  }

  private detectPotentialMemoryLeaks(counters: { documents: number; nodes: number; jsEventListeners: number }): string[] {
    const leaks: string[] = [];
    
    if (counters.nodes > 10000) {
      leaks.push("High DOM node count - potential memory leak");
    }
    
    if (counters.jsEventListeners > 1000) {
      leaks.push("High event listener count - check for proper cleanup");
    }
    
    return leaks;
  }
}

interface PerformanceReport {
  coreWebVitals: CoreWebVitals;
  resourceMetrics: ResourceMetrics;
  runtimeMetrics: RuntimeMetrics;
  memoryAnalysis: MemoryMetrics;
  recommendations: string[];
}

interface CoreWebVitals {
  lcp: number | null; // Largest Contentful Paint
  fid: number | null; // First Input Delay
  cls: number; // Cumulative Layout Shift
}

interface ResourceMetrics {
  totalLoadTime: number;
  criticalResourceCount: number;
  renderBlockingResources: number;
}

interface RuntimeMetrics {
  jsExecutionTime: number;
  layoutCount: number;
  styleRecalcCount: number;
  mainThreadBlockingTime: number;
}

interface MemoryMetrics {
  domCounters: { documents: number; nodes: number; jsEventListeners: number };
  heapSize: number;
  memoryLeaks: string[];
}