"""Heatmap collector for Python profiling with line-level execution heat visualization."""
import base64
import collections
import html
import importlib.resources
import json
import locale
import math
import os
import platform
import site
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Dict, List, Tuple
from ._css_utils import get_combined_css
from ._format_utils import fmt
from .collector import normalize_location, extract_lineno
from .opcode_utils import get_opcode_info, format_opcode
from .stack_collector import StackTraceCollector
from .module_utils import extract_module_name, get_python_path_info
# ============================================================================
# Data Classes
# ============================================================================
@dataclass
class FileStats:
"""Statistics for a single profiled file."""
filename: str
module_name: str
module_type: str
total_samples: int
total_self_samples: int
num_lines: int
max_samples: int
max_self_samples: int
percentage: float = 0.0
@dataclass
class TreeNode:
"""Node in the hierarchical file tree structure."""
files: List[FileStats] = field(default_factory=list)
samples: int = 0
count: int = 0
children: Dict[str, 'TreeNode'] = field(default_factory=dict)
# ============================================================================
# Helper Classes
# ============================================================================
class _TemplateLoader:
"""Loads and caches HTML/CSS/JS templates for heatmap generation."""
def __init__(self):
"""Load all templates and assets once."""
self.index_template = None
self.file_template = None
self.index_css = None
self.index_js = None
self.file_css = None
self.file_js = None
self.logo_html = None
self._load_templates()
def _load_templates(self):
"""Load all template files from _heatmap_assets."""
try:
template_dir = importlib.resources.files(__package__)
assets_dir = template_dir / "_heatmap_assets"
# Load HTML templates
self.index_template = (assets_dir / "heatmap_index_template.html").read_text(encoding="utf-8")
self.file_template = (assets_dir / "heatmap_pyfile_template.html").read_text(encoding="utf-8")
# Load CSS (same file used for both index and file pages)
css_content = get_combined_css("heatmap")
self.index_css = css_content
self.file_css = css_content
# Load JS
base_js = (template_dir / "_shared_assets" / "base.js").read_text(encoding="utf-8")
heatmap_shared_js = (assets_dir / "heatmap_shared.js").read_text(encoding="utf-8")
shared_js = f"{base_js}\n{heatmap_shared_js}"
self.index_js = f"{shared_js}\n{(assets_dir / 'heatmap_index.js').read_text(encoding='utf-8')}"
self.file_js = f"{shared_js}\n{(assets_dir / 'heatmap.js').read_text(encoding='utf-8')}"
# Load Tachyon logo
logo_dir = template_dir / "_assets"
try:
png_path = logo_dir / "tachyon-logo.png"
b64_logo = base64.b64encode(png_path.read_bytes()).decode("ascii")
self.logo_html = f''
except (FileNotFoundError, IOError) as e:
self.logo_html = '