/** * Sampling Profiler Visualization */ (function () { "use strict"; // ============================================================================ // Configuration // ============================================================================ const TIMINGS = { sampleIntervalMin: 100, sampleIntervalMax: 500, sampleIntervalDefault: 200, sampleToFlame: 600, defaultSpeed: 0.05, }; const LAYOUT = { frameSpacing: 6 }; // Function name to color mapping const FUNCTION_COLORS = { main: "#306998", fibonacci: "#D4A910", add: "#E65100", multiply: "#7B1FA2", calculate: "#D4A910", }; const DEFAULT_FUNCTION_COLOR = "#306998"; // Easing functions - cubic-bezier approximations const EASING_MAP = { linear: "linear", easeOutQuad: "cubic-bezier(0.25, 0.46, 0.45, 0.94)", easeOutCubic: "cubic-bezier(0.215, 0.61, 0.355, 1)", }; function getFunctionColor(funcName) { return FUNCTION_COLORS[funcName] || DEFAULT_FUNCTION_COLOR; } // ============================================================================ // Animation Manager // ============================================================================ class AnimationManager { constructor() { this.activeAnimations = new Set(); } to(element, props, duration, easing = "easeOutQuad", onComplete = null) { this.killAnimationsOf(element); const cssEasing = EASING_MAP[easing] || EASING_MAP.easeOutQuad; const transformProps = {}; const otherProps = {}; for (const [key, value] of Object.entries(props)) { if (key === "position") { if (typeof value.x === "number") transformProps.x = value.x; if (typeof value.y === "number") transformProps.y = value.y; } else if (key === "x" || key === "y") { transformProps[key] = value; } else if (key === "scale") { transformProps.scale = value; } else if (key === "alpha" || key === "opacity") { otherProps.opacity = value; } else { otherProps[key] = value; } } const computedStyle = getComputedStyle(element); const matrix = new DOMMatrix(computedStyle.transform); const currentScale = Math.sqrt( matrix.m11 * matrix.m11 + matrix.m21 * matrix.m21, ); transformProps.x ??= matrix.m41; transformProps.y ??= matrix.m42; transformProps.scale ??= currentScale; const initialTransform = this._buildTransformString( matrix.m41, matrix.m42, currentScale, ); const finalTransform = this._buildTransformString( transformProps.x, transformProps.y, transformProps.scale, ); const initialKeyframe = { transform: initialTransform }; const finalKeyframe = { transform: finalTransform }; for (const [key, value] of Object.entries(otherProps)) { const currentVal = key === "opacity" ? element.style.opacity || computedStyle.opacity : element.style[key]; initialKeyframe[key] = currentVal; finalKeyframe[key] = value; } const animation = element.animate([initialKeyframe, finalKeyframe], { duration, easing: cssEasing, fill: "forwards", }); this.activeAnimations.add(animation); animation.onfinish = () => { this.activeAnimations.delete(animation); element.style.transform = finalTransform; for (const [key, value] of Object.entries(finalKeyframe)) { if (key !== "transform") { element.style[key] = typeof value === "number" ? `${value}` : value; } } if (onComplete) onComplete(); }; return animation; } killAnimationsOf(element) { element.getAnimations().forEach((animation) => animation.cancel()); this.activeAnimations.forEach((animation) => { if (animation.effect && animation.effect.target === element) { animation.cancel(); this.activeAnimations.delete(animation); } }); } _buildTransformString(x, y, scale = 1) { return `translate(${x}px, ${y}px) scale(${scale})`; } } const anim = new AnimationManager(); // ============================================================================ // Execution Trace Model // ============================================================================ class ExecutionEvent { constructor( type, functionName, lineno, timestamp, args = null, value = null, ) { this.type = type; this.functionName = functionName; this.lineno = lineno; this.timestamp = timestamp; this.args = args; this.value = value; } } class ExecutionTrace { constructor(source, events) { this.source = source; this.events = events.map( (e) => new ExecutionEvent(e.type, e.func, e.line, e.ts, e.args, e.value), ); this.duration = events.length > 0 ? events[events.length - 1].ts : 0; } getEventsUntil(timestamp) { return this.events.filter((e) => e.timestamp <= timestamp); } getStackAt(timestamp) { const stack = []; const events = this.getEventsUntil(timestamp); for (const event of events) { if (event.type === "call") { stack.push({ func: event.functionName, line: event.lineno, args: event.args, }); } else if (event.type === "return") { stack.pop(); } else if (event.type === "line") { if (stack.length > 0) { stack[stack.length - 1].line = event.lineno; } } } return stack; } getNextEvent(timestamp) { return this.events.find((e) => e.timestamp > timestamp); } getSourceLines() { return this.source.split("\n"); } } // ============================================================================ // Demo Data // ============================================================================ // This placeholder is replaced by the profiling_trace Sphinx extension // during the documentation build with dynamically generated trace data. const DEMO_SIMPLE = /* PROFILING_TRACE_DATA */ null; // ============================================================================ // Code Panel Component // ============================================================================ class CodePanel { constructor(source) { this.source = source; this.currentLine = null; this.element = document.createElement("div"); this.element.id = "code-panel"; const title = document.createElement("div"); title.className = "code-panel-title"; title.textContent = "source code"; this.element.appendChild(title); this.codeContainer = document.createElement("pre"); this.codeContainer.className = "code-container"; this.element.appendChild(this.codeContainer); this._renderSource(); } updateSource(source) { this.source = source; this.codeContainer.innerHTML = ""; this._renderSource(); this.currentLine = null; } _renderSource() { const lines = this.source.split("\n"); lines.forEach((line, index) => { const lineNumber = index + 1; const lineDiv = document.createElement("div"); lineDiv.className = "line"; lineDiv.dataset.line = lineNumber; const lineNumSpan = document.createElement("span"); lineNumSpan.className = "line-number"; lineNumSpan.textContent = lineNumber; lineDiv.appendChild(lineNumSpan); const codeSpan = document.createElement("span"); codeSpan.className = "line-content"; codeSpan.innerHTML = this._highlightSyntax(line); lineDiv.appendChild(codeSpan); this.codeContainer.appendChild(lineDiv); }); } _highlightSyntax(line) { return line .replace(/&/g, "&") .replace(//g, ">") .replace(/(f?"[^"]*"|f?'[^']*')/g, '$1') .replace(/(#.*$)/g, '$1') .replace( /\b(def|if|elif|else|return|for|in|range|print|__name__|__main__)\b/g, '$1', ) .replace( /def<\/span>\s+(\w+)/g, 'def $1', ) .replace(/\b(\d+)\b/g, '$1'); } highlightLine(lineNumber) { if (this.currentLine === lineNumber) return; if (this.currentLine !== null) { const prevLine = this.codeContainer.querySelector( `[data-line="${this.currentLine}"]`, ); if (prevLine) prevLine.classList.remove("highlighted"); } if (lineNumber === null || lineNumber === undefined) { this.currentLine = null; return; } this.currentLine = lineNumber; const newLine = this.codeContainer.querySelector( `[data-line="${lineNumber}"]`, ); if (newLine) { newLine.classList.add("highlighted"); } } reset() { this.highlightLine(null); this.codeContainer.scrollTop = 0; } destroy() { this.element.remove(); } } // ============================================================================ // Stack Frame Component // ============================================================================ class DOMStackFrame { constructor(functionName, lineno, args = null) { this.functionName = functionName; this.lineno = lineno; this.args = args; this.isActive = false; this.color = getFunctionColor(functionName); this.element = document.createElement("div"); this.element.className = "stack-frame"; this.element.dataset.function = functionName; this.bgElement = document.createElement("div"); this.bgElement.className = "stack-frame-bg"; this.bgElement.style.backgroundColor = this.color; this.element.appendChild(this.bgElement); this.textElement = document.createElement("span"); this.textElement.className = "stack-frame-text"; this.textElement.textContent = functionName; this.element.appendChild(this.textElement); this.flashElement = document.createElement("div"); this.flashElement.className = "stack-frame-flash"; this.element.appendChild(this.flashElement); this.element.addEventListener("pointerover", this._onHover.bind(this)); this.element.addEventListener("pointerout", this._onHoverOut.bind(this)); } destroy() { this.element.parentNode?.removeChild(this.element); } updateLine(lineno) { this.lineno = lineno; this.textElement.textContent = this.functionName; } setActive(isActive) { if (this.isActive === isActive) return; this.isActive = isActive; this.bgElement.style.opacity = isActive ? "1.0" : "0.9"; } _onHover() { this.bgElement.style.opacity = "0.8"; } _onHoverOut() { this.bgElement.style.opacity = this.isActive ? "1.0" : "0.9"; } flash(duration = 150) { this.flashElement.animate([{ opacity: 1 }, { opacity: 0 }], { duration, easing: "ease-out", }); } getPosition() { const rect = this.element.getBoundingClientRect(); return { x: rect.left, y: rect.top }; } } // ============================================================================ // Stack Visualization Component // ============================================================================ class DOMStackVisualization { constructor() { this.frames = []; this.frameSpacing = LAYOUT.frameSpacing; this.element = document.createElement("div"); this.element.className = "stack-visualization"; } processEvent(event) { if (event.type === "call") { this.pushFrame(event.functionName, event.lineno, event.args); } else if (event.type === "return") { this.popFrame(); } else if (event.type === "line") { this.updateTopFrameLine(event.lineno); } } updateTopFrameLine(lineno) { if (this.frames.length > 0) { this.frames[this.frames.length - 1].updateLine(lineno); } } pushFrame(functionName, lineno, args = null) { if (this.frames.length > 0) { this.frames[this.frames.length - 1].setActive(false); } const frame = new DOMStackFrame(functionName, lineno, args); frame.setActive(true); this.element.appendChild(frame.element); this.frames.push(frame); requestAnimationFrame(() => { frame.element.classList.add("visible"); }); } popFrame() { if (this.frames.length === 0) return; const frame = this.frames.pop(); frame.element.classList.remove("visible"); setTimeout(() => frame.destroy(), 300); if (this.frames.length > 0) { this.frames[this.frames.length - 1].setActive(true); } } clear() { this.frames.forEach((frame) => frame.destroy()); this.frames = []; this.element.innerHTML = ""; } flashAll() { this.frames.forEach((frame) => frame.flash()); } createStackClone(container) { const clone = this.element.cloneNode(false); clone.className = "stack-visualization flying-clone"; const elementRect = this.element.getBoundingClientRect(); const containerRect = container.getBoundingClientRect(); // Position relative to container since contain: strict makes position:fixed relative to container clone.style.position = "absolute"; clone.style.left = elementRect.left - containerRect.left + "px"; clone.style.top = elementRect.top - containerRect.top + "px"; clone.style.width = elementRect.width + "px"; clone.style.pointerEvents = "none"; clone.style.zIndex = "1000"; this.frames.forEach((frame) => { const frameClone = frame.element.cloneNode(true); frameClone.classList.add("visible"); frameClone.style.opacity = "1"; frameClone.style.transform = "translateY(0)"; frameClone.style.transition = "none"; clone.appendChild(frameClone); }); container.appendChild(clone); return clone; } updateToMatch(targetStack) { while (this.frames.length > targetStack.length) { this.popFrame(); } targetStack.forEach(({ func, line, args }, index) => { if (index < this.frames.length) { const frame = this.frames[index]; if (frame.functionName !== func) { frame.updateLine(line); } frame.setActive(index === targetStack.length - 1); } else { this.pushFrame(func, line, args); } }); if (this.frames.length > 0) { this.frames[this.frames.length - 1].setActive(true); } } } // ============================================================================ // Sampling Panel Component // ============================================================================ class DOMSamplingPanel { constructor() { this.samples = []; this.functionCounts = {}; this.totalSamples = 0; this.sampleInterval = TIMINGS.sampleIntervalDefault; this.groundTruthFunctions = new Set(); this.bars = {}; this.element = document.createElement("div"); this.element.className = "sampling-panel"; const header = document.createElement("div"); header.className = "sampling-header"; const title = document.createElement("h3"); title.className = "sampling-title"; title.textContent = "Sampling Profiler"; header.appendChild(title); const stats = document.createElement("div"); stats.className = "sampling-stats"; this.sampleCountEl = document.createElement("span"); this.sampleCountEl.textContent = "Samples: 0"; stats.appendChild(this.sampleCountEl); this.intervalEl = document.createElement("span"); this.intervalEl.textContent = `Interval: ${this.sampleInterval}ms`; stats.appendChild(this.intervalEl); this.missedFunctionsEl = document.createElement("span"); this.missedFunctionsEl.className = "missed"; stats.appendChild(this.missedFunctionsEl); header.appendChild(stats); this.element.appendChild(header); this.barsContainer = document.createElement("div"); this.barsContainer.className = "sampling-bars"; this.element.appendChild(this.barsContainer); } setSampleInterval(interval) { this.sampleInterval = interval; this.intervalEl.textContent = `Interval: ${interval}ms`; } setGroundTruth(allFunctions) { this.groundTruthFunctions = new Set(allFunctions); this._updateMissedCount(); } addSample(stack) { this.totalSamples++; this.sampleCountEl.textContent = `Samples: ${this.totalSamples}`; stack.forEach((frame) => { const funcName = frame.func; this.functionCounts[funcName] = (this.functionCounts[funcName] || 0) + 1; }); this._updateBars(); this._updateMissedCount(); } reset() { this.samples = []; this.functionCounts = {}; this.totalSamples = 0; this.sampleCountEl.textContent = "Samples: 0"; this.missedFunctionsEl.textContent = ""; this.barsContainer.innerHTML = ""; this.bars = {}; } _updateMissedCount() { if (this.groundTruthFunctions.size === 0) return; const capturedFunctions = new Set(Object.keys(this.functionCounts)); const notYetSeen = [...this.groundTruthFunctions].filter( (f) => !capturedFunctions.has(f), ); if (notYetSeen.length > 0) { this.missedFunctionsEl.textContent = `Not yet seen: ${notYetSeen.length}`; this.missedFunctionsEl.classList.add("missed"); this.missedFunctionsEl.style.color = ""; } else if (this.totalSamples > 0) { this.missedFunctionsEl.textContent = "All captured!"; this.missedFunctionsEl.classList.remove("missed"); this.missedFunctionsEl.style.color = "var(--color-green)"; } else { this.missedFunctionsEl.textContent = ""; } } _updateBars() { const sorted = Object.entries(this.functionCounts).sort( (a, b) => b[1] - a[1], ); sorted.forEach(([funcName, count], index) => { const percentage = this.totalSamples > 0 ? count / this.totalSamples : 0; if (!this.bars[funcName]) { const row = this._createBarRow(funcName); this.barsContainer.appendChild(row); this.bars[funcName] = row; } const row = this.bars[funcName]; const barFill = row.querySelector(".bar-fill"); barFill.style.width = `${percentage * 100}%`; const percentEl = row.querySelector(".bar-percent"); percentEl.textContent = `${(percentage * 100).toFixed(0)}%`; const currentIndex = Array.from(this.barsContainer.children).indexOf( row, ); if (currentIndex !== index) { this.barsContainer.insertBefore( row, this.barsContainer.children[index], ); } }); } _createBarRow(funcName) { const row = document.createElement("div"); row.className = "sampling-bar-row"; row.dataset.function = funcName; const label = document.createElement("span"); label.className = "bar-label"; label.textContent = funcName; row.appendChild(label); const barContainer = document.createElement("div"); barContainer.className = "bar-container"; const barFill = document.createElement("div"); barFill.className = "bar-fill"; barFill.style.backgroundColor = getFunctionColor(funcName); barContainer.appendChild(barFill); row.appendChild(barContainer); const percent = document.createElement("span"); percent.className = "bar-percent"; percent.textContent = "0%"; row.appendChild(percent); return row; } getTargetPosition() { const rect = this.barsContainer.getBoundingClientRect(); return { x: rect.left + rect.width / 2, y: rect.top + 50 }; } showImpactEffect(position) { const impact = document.createElement("div"); impact.className = "impact-circle"; impact.style.position = "fixed"; impact.style.left = `${position.x}px`; impact.style.top = `${position.y}px`; // Append to barsContainer parent to avoid triggering scroll this.element.appendChild(impact); impact.animate( [ { transform: "translate(-50%, -50%) scale(1)", opacity: 0.6 }, { transform: "translate(-50%, -50%) scale(4)", opacity: 0 }, ], { duration: 300, easing: "ease-out", }, ).onfinish = () => impact.remove(); } } // ============================================================================ // Control Panel Component // ============================================================================ class ControlPanel { constructor( container, onPlay, onPause, onReset, onSpeedChange, onSeek, onStep, onSampleIntervalChange = null, ) { this.container = container; this.onPlay = onPlay; this.onPause = onPause; this.onReset = onReset; this.onSpeedChange = onSpeedChange; this.onSeek = onSeek; this.onStep = onStep; this.onSampleIntervalChange = onSampleIntervalChange; this.isPlaying = false; this.speed = TIMINGS.defaultSpeed; this._createControls(); } _createControls() { const panel = document.createElement("div"); panel.id = "control-panel"; const sampleIntervalHtml = this.onSampleIntervalChange ? `
${TIMINGS.sampleIntervalDefault}ms
` : ""; panel.innerHTML = `
${sampleIntervalHtml}
0ms
`; this.container.appendChild(panel); this.playPauseBtn = panel.querySelector("#play-pause-btn"); this.resetBtn = panel.querySelector("#reset-btn"); this.stepBtn = panel.querySelector("#step-btn"); this.scrubber = panel.querySelector("#timeline-scrubber"); this.timeDisplay = panel.querySelector("#time-display"); this.playPauseBtn.addEventListener("click", () => this._togglePlayPause(), ); this.resetBtn.addEventListener("click", () => this._handleReset()); this.stepBtn.addEventListener("click", () => this._handleStep()); this.scrubber.addEventListener("input", (e) => this._handleSeek(e)); if (this.onSampleIntervalChange) { this.sampleIntervalSlider = panel.querySelector("#sample-interval"); this.intervalDisplay = panel.querySelector("#interval-display"); this.sampleIntervalSlider.addEventListener("input", (e) => this._handleSampleIntervalChange(e), ); } } _handleSampleIntervalChange(e) { const interval = parseInt(e.target.value); this.intervalDisplay.textContent = `${interval}ms`; this.onSampleIntervalChange(interval); } _togglePlayPause() { this.isPlaying = !this.isPlaying; if (this.isPlaying) { this.playPauseBtn.textContent = "⏸ Pause"; this.playPauseBtn.classList.add("active"); this.onPlay(); } else { this.playPauseBtn.textContent = "▶ Play"; this.playPauseBtn.classList.remove("active"); this.onPause(); } } _handleReset() { this.isPlaying = false; this.playPauseBtn.textContent = "▶ Play"; this.playPauseBtn.classList.remove("active"); this.scrubber.value = 0; this.timeDisplay.textContent = "0ms"; this.onReset(); } _handleStep() { if (this.onStep) this.onStep(); } _handleSeek(e) { const percentage = parseFloat(e.target.value); this.onSeek(percentage / 100); } updateTimeDisplay(currentTime, totalTime) { this.timeDisplay.textContent = `${Math.floor(currentTime)}ms / ${Math.floor(totalTime)}ms`; const percentage = (currentTime / totalTime) * 100; this.scrubber.value = percentage; } setDuration(duration) { this.duration = duration; } pause() { if (this.isPlaying) this._togglePlayPause(); } destroy() { const panel = this.container.querySelector("#control-panel"); if (panel) panel.remove(); } } // ============================================================================ // Visual Effects Manager // ============================================================================ class VisualEffectsManager { constructor(container) { this.container = container; this.flyingAnimationInProgress = false; this.flashOverlay = document.createElement("div"); this.flashOverlay.className = "flash-overlay"; this.container.appendChild(this.flashOverlay); } triggerSamplingEffect(stackViz, samplingPanel, currentTime, trace) { if (this.flyingAnimationInProgress) return; const stack = trace.getStackAt(currentTime); if (stack.length === 0) { samplingPanel.addSample(stack); return; } this.flyingAnimationInProgress = true; stackViz.flashAll(); const clone = stackViz.createStackClone(this.container); const targetPosition = samplingPanel.getTargetPosition(); this._animateFlash(); this._animateFlyingStack(clone, targetPosition, () => { samplingPanel.showImpactEffect(targetPosition); clone.remove(); const currentStack = trace.getStackAt(currentTime); samplingPanel.addSample(currentStack); this.flyingAnimationInProgress = false; }); } _animateFlash() { anim.to(this.flashOverlay, { opacity: 0.1 }, 0).onfinish = () => { anim.to(this.flashOverlay, { opacity: 0 }, 150, "easeOutQuad"); }; } _animateFlyingStack(clone, targetPosition, onComplete) { const containerRect = this.container.getBoundingClientRect(); const cloneRect = clone.getBoundingClientRect(); // Convert viewport coordinates to container-relative const startX = cloneRect.left - containerRect.left + cloneRect.width / 2; const startY = cloneRect.top - containerRect.top + cloneRect.height / 2; const targetX = targetPosition.x - containerRect.left; const targetY = targetPosition.y - containerRect.top; const deltaX = targetX - startX; const deltaY = targetY - startY; anim.to( clone, { x: deltaX, y: deltaY, scale: 0.3, opacity: 0.6, }, TIMINGS.sampleToFlame, "easeOutCubic", onComplete, ); } } // ============================================================================ // Main Visualization Class // ============================================================================ class SamplingVisualization { constructor(container) { this.container = container; this.trace = new ExecutionTrace(DEMO_SIMPLE.source, DEMO_SIMPLE.trace); this.currentTime = 0; this.isPlaying = false; this.playbackSpeed = TIMINGS.defaultSpeed; this.eventIndex = 0; this.sampleInterval = TIMINGS.sampleIntervalDefault; this.lastSampleTime = 0; this._createLayout(); this.effectsManager = new VisualEffectsManager(this.vizColumn); this.lastTime = performance.now(); this._animate(); } _createLayout() { this.codePanel = new CodePanel(this.trace.source); this.container.appendChild(this.codePanel.element); this.vizColumn = document.createElement("div"); this.vizColumn.className = "viz-column"; this.container.appendChild(this.vizColumn); const stackSection = document.createElement("div"); stackSection.className = "stack-section"; const stackTitle = document.createElement("div"); stackTitle.className = "stack-section-title"; stackTitle.textContent = "Call Stack"; stackSection.appendChild(stackTitle); this.stackViz = new DOMStackVisualization(); stackSection.appendChild(this.stackViz.element); this.vizColumn.appendChild(stackSection); this.samplingPanel = new DOMSamplingPanel(); this.samplingPanel.setGroundTruth(this._getGroundTruthFunctions()); this.vizColumn.appendChild(this.samplingPanel.element); this.controls = new ControlPanel( this.vizColumn, () => this.play(), () => this.pause(), () => this.reset(), (speed) => this.setSpeed(speed), (progress) => this.seek(progress), () => this.step(), (interval) => this.setSampleInterval(interval), ); this.controls.setDuration(this.trace.duration); } _getGroundTruthFunctions() { const functions = new Set(); this.trace.events.forEach((event) => { if (event.type === "call") { functions.add(event.functionName); } }); return [...functions]; } play() { this.isPlaying = true; } pause() { this.isPlaying = false; } reset() { this.currentTime = 0; this.eventIndex = 0; this.isPlaying = false; this.lastSampleTime = 0; this.stackViz.clear(); this.codePanel.reset(); this.samplingPanel.reset(); this.controls.updateTimeDisplay(0, this.trace.duration); } setSpeed(speed) { this.playbackSpeed = speed; } setSampleInterval(interval) { this.sampleInterval = interval; this.samplingPanel.setSampleInterval(interval); } seek(progress) { this.currentTime = progress * this.trace.duration; this.eventIndex = 0; this.lastSampleTime = 0; this._rebuildState(); } step() { this.pause(); const nextEvent = this.trace.getNextEvent(this.currentTime); if (nextEvent) { // Calculate delta to reach next event + epsilon const targetTime = nextEvent.timestamp + 0.1; const delta = targetTime - this.currentTime; if (delta > 0) { this._advanceTime(delta); } } } _animate(currentTime = performance.now()) { const deltaTime = currentTime - this.lastTime; this.lastTime = currentTime; this.update(deltaTime); requestAnimationFrame((t) => this._animate(t)); } update(deltaTime) { if (!this.isPlaying) { this.controls.updateTimeDisplay(this.currentTime, this.trace.duration); return; } const virtualDelta = deltaTime * this.playbackSpeed; this._advanceTime(virtualDelta); } _advanceTime(virtualDelta) { this.currentTime += virtualDelta; if (this.currentTime >= this.trace.duration) { this.currentTime = this.trace.duration; this.isPlaying = false; this.controls.pause(); } while (this.eventIndex < this.trace.events.length) { const event = this.trace.events[this.eventIndex]; if (event.timestamp > this.currentTime) break; this._processEvent(event); this.eventIndex++; } this.controls.updateTimeDisplay(this.currentTime, this.trace.duration); if (this.currentTime - this.lastSampleTime >= this.sampleInterval) { this._takeSample(); this.lastSampleTime = this.currentTime; } } _processEvent(event) { this.stackViz.processEvent(event); if (event.type === "call") { this.codePanel.highlightLine(event.lineno); } else if (event.type === "return") { const currentStack = this.trace.getStackAt(this.currentTime); if (currentStack.length > 0) { this.codePanel.highlightLine( currentStack[currentStack.length - 1].line, ); } else { this.codePanel.highlightLine(null); } } else if (event.type === "line") { this.codePanel.highlightLine(event.lineno); } } _takeSample() { this.effectsManager.triggerSamplingEffect( this.stackViz, this.samplingPanel, this.currentTime, this.trace, ); } _rebuildState() { this.stackViz.clear(); this.codePanel.reset(); this.samplingPanel.reset(); for (let t = 0; t < this.currentTime; t += this.sampleInterval) { const stack = this.trace.getStackAt(t); this.samplingPanel.addSample(stack); this.lastSampleTime = t; } const stack = this.trace.getStackAt(this.currentTime); this.stackViz.updateToMatch(stack); if (stack.length > 0) { this.codePanel.highlightLine(stack[stack.length - 1].line); } this.eventIndex = this.trace.getEventsUntil(this.currentTime).length; } } // ============================================================================ // Initialize // ============================================================================ function init() { // If trace data hasn't been injected yet (local dev), don't initialize if (!DEMO_SIMPLE) return; const appContainer = document.getElementById("sampling-profiler-viz"); if (appContainer) { new SamplingVisualization(appContainer); } } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })();