CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-scheduler

Cooperative scheduler for the browser environment that provides time-slicing capabilities for JavaScript applications.

Pending
Overview
Eval results
Files

profiling.mddocs/

Profiling

Performance monitoring and event logging capabilities for development and debugging of scheduler-based applications.

The profiling system provides detailed insights into task execution, timing, and scheduler behavior through structured event logging.

Capabilities

Profiling Interface

The main profiling interface provides methods to start and stop profiling event collection.

/**
 * Profiling interface for performance monitoring
 * Available only in profiling builds, null in production builds
 */
const unstable_Profiling: {
  /** Start collecting profiling events */
  startLoggingProfilingEvents(): void;
  /** Stop collecting events and return logged data */
  stopLoggingProfilingEvents(): ArrayBuffer | null;
} | null;

Usage Examples:

import { unstable_Profiling } from "scheduler";

// Check if profiling is available
if (unstable_Profiling !== null) {
  // Start profiling
  unstable_Profiling.startLoggingProfilingEvents();
  
  // Perform operations to profile
  performScheduledWork();
  
  // Stop profiling and get data
  const profilingData = unstable_Profiling.stopLoggingProfilingEvents();
  
  if (profilingData) {
    // Process profiling data (ArrayBuffer)
    analyzeProfilingData(profilingData);
  }
} else {
  console.log("Profiling not available in this build");
}

// Conditional profiling wrapper
function withProfiling(operation, name) {
  const profilingEnabled = unstable_Profiling !== null;
  
  if (profilingEnabled) {
    unstable_Profiling.startLoggingProfilingEvents();
  }
  
  try {
    return operation();
  } finally {
    if (profilingEnabled) {
      const data = unstable_Profiling.stopLoggingProfilingEvents();
      console.log(`Profiling data for ${name}:`, data);
    }
  }
}

Profiling Data Format

The profiling system logs events as structured data in an ArrayBuffer. Events are logged as 32-bit integers with the following format:

  • Event Type (1 byte): Type of event that occurred
  • Timestamp (4 bytes): Microsecond timestamp when event occurred
  • Task ID (4 bytes): Unique identifier for the task
  • Additional Data (varies): Priority level, run ID, or other event-specific data

Event Types:

// Event type constants (internal)
const TaskStartEvent = 1;      // Task added to queue
const TaskCompleteEvent = 2;   // Task finished successfully  
const TaskErrorEvent = 3;      // Task threw an error
const TaskCancelEvent = 4;     // Task was cancelled
const TaskRunEvent = 5;        // Task started executing
const TaskYieldEvent = 6;      // Task yielded control
const SchedulerSuspendEvent = 7;   // Scheduler became idle
const SchedulerResumeEvent = 8;    // Scheduler resumed work

Profiling Events

The scheduler automatically logs various events during task execution when profiling is enabled:

Task Lifecycle Events

  • Task Start: When a task is scheduled and added to the queue
  • Task Run: When a task begins executing
  • Task Yield: When a task yields control back to scheduler
  • Task Complete: When a task finishes successfully
  • Task Error: When a task throws an exception
  • Task Cancel: When a task is cancelled before completion

Scheduler Events

  • Scheduler Suspend: When scheduler becomes idle (no pending work)
  • Scheduler Resume: When scheduler resumes after being idle

Advanced Usage

Profiling Specific Operations

import { 
  unstable_Profiling,
  unstable_scheduleCallback,
  unstable_NormalPriority 
} from "scheduler";

async function profileTaskExecution() {
  if (!unstable_Profiling) return;
  
  unstable_Profiling.startLoggingProfilingEvents();
  
  // Schedule multiple tasks to profile
  const tasks = [];
  for (let i = 0; i < 10; i++) {
    const task = unstable_scheduleCallback(unstable_NormalPriority, (didTimeout) => {
      // Simulate work
      const start = performance.now();
      while (performance.now() - start < 5) {
        // Busy wait for 5ms
      }
      
      if (i % 3 === 0) {
        // Some tasks yield
        return (didTimeout) => {
          console.log(`Task ${i} continuation, timeout: ${didTimeout}`);
        };
      }
    });
    tasks.push(task);
  }
  
  // Let tasks execute, then collect profiling data
  setTimeout(() => {
    const profilingData = unstable_Profiling.stopLoggingProfilingEvents();
    console.log("Profiling completed, data size:", profilingData?.byteLength);
  }, 1000);
}

Profiling Data Analysis

function analyzeProfilingData(arrayBuffer) {
  if (!arrayBuffer || arrayBuffer.byteLength === 0) {
    console.log("No profiling data available");
    return;
  }
  
  const view = new Int32Array(arrayBuffer);
  const events = [];
  
  // Parse events from the buffer
  for (let i = 0; i < view.length; i += 4) {
    const eventType = view[i];
    const timestamp = view[i + 1];
    const taskId = view[i + 2];
    const extraData = view[i + 3];
    
    events.push({
      type: getEventTypeName(eventType),
      timestamp: timestamp / 1000, // Convert microseconds to milliseconds
      taskId,
      extraData
    });
  }
  
  // Analyze the events
  console.log(`Collected ${events.length} profiling events`);
  
  const taskMetrics = calculateTaskMetrics(events);
  console.log("Task metrics:", taskMetrics);
}

function getEventTypeName(eventType) {
  const names = {
    1: "TaskStart",
    2: "TaskComplete", 
    3: "TaskError",
    4: "TaskCancel",
    5: "TaskRun",
    6: "TaskYield",
    7: "SchedulerSuspend",
    8: "SchedulerResume"
  };
  return names[eventType] || "Unknown";
}

function calculateTaskMetrics(events) {
  const taskStats = new Map();
  
  events.forEach(event => {
    if (!taskStats.has(event.taskId)) {
      taskStats.set(event.taskId, {
        startTime: null,
        endTime: null,
        runTime: null,
        yields: 0,
        errors: 0
      });
    }
    
    const stats = taskStats.get(event.taskId);
    
    switch (event.type) {
      case "TaskStart":
        stats.startTime = event.timestamp;
        break;
      case "TaskRun":
        stats.runTime = event.timestamp;
        break;
      case "TaskComplete":
        stats.endTime = event.timestamp;
        break;
      case "TaskYield":
        stats.yields++;
        break;
      case "TaskError":
        stats.errors++;
        break;
    }
  });
  
  return {
    totalTasks: taskStats.size,
    avgExecutionTime: calculateAverageExecutionTime(taskStats),
    totalYields: Array.from(taskStats.values()).reduce((sum, stats) => sum + stats.yields, 0),
    totalErrors: Array.from(taskStats.values()).reduce((sum, stats) => sum + stats.errors, 0)
  };
}

Integration with Development Tools

// Development-only profiling wrapper
function createProfilingWrapper() {
  if (process.env.NODE_ENV !== "development" || !unstable_Profiling) {
    return {
      start: () => {},
      end: () => {},
      wrap: (fn) => fn
    };
  }
  
  let isActive = false;
  
  return {
    start(label = "operation") {
      if (isActive) return;
      isActive = true;
      console.log(`Starting profiling: ${label}`);
      unstable_Profiling.startLoggingProfilingEvents();
    },
    
    end(label = "operation") {
      if (!isActive) return;
      isActive = false;
      const data = unstable_Profiling.stopLoggingProfilingEvents();
      console.log(`Profiling completed: ${label}`, data);
    },
    
    wrap(fn, label) {
      return (...args) => {
        this.start(label);
        try {
          return fn(...args);
        } finally {
          this.end(label);
        }
      };
    }
  };
}

// Usage
const profiler = createProfilingWrapper();

const profiledFunction = profiler.wrap(
  () => {
    // Scheduled work
    performComplexOperation();
  },
  "complex-operation"
);

Build Configuration

Profiling is controlled at build time through feature flags:

  • Development builds: Profiling typically enabled
  • Production builds: Profiling disabled (unstable_Profiling is null)
  • Profiling builds: Special builds with profiling always enabled

The profiling system adds minimal overhead when disabled, but can impact performance when enabled due to event logging overhead.

Limitations

  1. Build Dependency: Only available in builds with profiling enabled
  2. Memory Usage: Profiling data accumulates in memory until stopped
  3. Performance Impact: Event logging adds overhead to task execution
  4. Data Size Limits: Maximum event log size is limited (2MB by default)
  5. Binary Format: Profiling data is in binary format requiring parsing

Best Practices

  • Enable profiling only during development and debugging
  • Stop profiling collection regularly to prevent memory buildup
  • Use profiling to identify performance bottlenecks in task scheduling
  • Combine with browser DevTools for comprehensive performance analysis
  • Profile both individual operations and complete workflows

Install with Tessl CLI

npx tessl i tessl/npm-scheduler

docs

core-scheduling.md

index.md

priority-management.md

profiling.md

testing-utilities.md

timing-yielding.md

tile.json