mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
gh-138122: New Tachyon UI (#142116)
Co-authored-by: Pablo Galindo Salgado <pablogsal@gmail.com>
This commit is contained in:
parent
52f9b5f580
commit
f87eb4d7cd
6 changed files with 2129 additions and 1167 deletions
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,9 +1,9 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="en" data-theme="light">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Python Performance Flamegraph</title>
|
||||
<title>Tachyon Profiler - Flamegraph</title>
|
||||
<!-- INLINE_VENDOR_D3_JS -->
|
||||
<!-- INLINE_VENDOR_FLAMEGRAPH_CSS -->
|
||||
<!-- INLINE_VENDOR_FLAMEGRAPH_JS -->
|
||||
|
|
@ -11,165 +11,297 @@
|
|||
<!-- INLINE_CSS -->
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<div class="header-content">
|
||||
<div class="python-logo"><!-- INLINE_LOGO --></div>
|
||||
<div class="header-text">
|
||||
<h1>Tachyon Profiler Performance Flamegraph</h1>
|
||||
<div class="subtitle">
|
||||
Interactive visualization of function call performance
|
||||
<div class="app-layout">
|
||||
<!-- Top Bar -->
|
||||
<header class="top-bar">
|
||||
<div class="brand">
|
||||
<span class="brand-text">Tachyon</span>
|
||||
<span class="brand-divider"></span>
|
||||
<span class="brand-subtitle">Profiler</span>
|
||||
</div>
|
||||
<div class="search-wrapper">
|
||||
<input
|
||||
type="text"
|
||||
id="search-input"
|
||||
class="search-input"
|
||||
placeholder="Search functions..."
|
||||
/>
|
||||
<button
|
||||
class="search-clear"
|
||||
id="search-clear"
|
||||
onclick="clearSearch()"
|
||||
title="Clear search"
|
||||
>×</button>
|
||||
</div>
|
||||
<div class="header-search">
|
||||
<input type="text" id="search-input" placeholder="🔍 Search functions..." />
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<button
|
||||
class="toolbar-btn"
|
||||
onclick="resetZoom()"
|
||||
title="Reset zoom"
|
||||
>⌂</button>
|
||||
<button
|
||||
class="toolbar-btn"
|
||||
onclick="exportSVG()"
|
||||
title="Export SVG"
|
||||
>↓</button>
|
||||
<button
|
||||
class="toolbar-btn theme-toggle"
|
||||
onclick="toggleTheme()"
|
||||
title="Toggle theme"
|
||||
id="theme-btn"
|
||||
>☾</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-content">
|
||||
<!-- Sidebar -->
|
||||
<aside class="sidebar" id="sidebar">
|
||||
<button
|
||||
class="sidebar-toggle"
|
||||
onclick="toggleSidebar()"
|
||||
title="Toggle sidebar"
|
||||
aria-label="Toggle sidebar"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M10 12L6 8L10 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="sidebar-resize-handle" id="sidebar-resize-handle"></div>
|
||||
|
||||
<div class="sidebar-content">
|
||||
<!-- Logo Section -->
|
||||
<div class="sidebar-logo">
|
||||
<div class="sidebar-logo-img"><!-- INLINE_LOGO --></div>
|
||||
</div>
|
||||
|
||||
<!-- Compact Thread Stats Bar -->
|
||||
<div class="thread-stats-bar" id="thread-stats-bar" style="display: none;">
|
||||
<span class="thread-stat-item" id="gil-held-stat">
|
||||
<span class="stat-label">🟢 GIL Held:</span>
|
||||
<span class="stat-value" id="gil-held-pct">--</span>
|
||||
</span>
|
||||
<span class="thread-stat-separator">│</span>
|
||||
<span class="thread-stat-item" id="gil-released-stat">
|
||||
<span class="stat-label">🔴 GIL Released:</span>
|
||||
<span class="stat-value" id="gil-released-pct">--</span>
|
||||
</span>
|
||||
<span class="thread-stat-separator">│</span>
|
||||
<span class="thread-stat-item" id="gil-waiting-stat">
|
||||
<span class="stat-label">🟡 Waiting:</span>
|
||||
<span class="stat-value" id="gil-waiting-pct">--</span>
|
||||
</span>
|
||||
<span class="thread-stat-separator">│</span>
|
||||
<span class="thread-stat-item" id="gc-stat">
|
||||
<span class="stat-label">🗑️ GC:</span>
|
||||
<span class="stat-value" id="gc-pct">--</span>
|
||||
</span>
|
||||
<!-- Profile Summary Section -->
|
||||
<section class="sidebar-section collapsible" id="summary-section">
|
||||
<button class="section-header" onclick="toggleSection('summary-section')">
|
||||
<h3 class="section-title">Profile Summary</h3>
|
||||
<svg class="section-chevron" width="12" height="12" viewBox="0 0 12 12" fill="none">
|
||||
<path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="section-content">
|
||||
<div class="summary-grid">
|
||||
<div class="summary-card" id="summary-samples">
|
||||
<div class="summary-icon">📊</div>
|
||||
<div class="summary-data">
|
||||
<div class="summary-value" id="stat-total-samples">--</div>
|
||||
<div class="summary-label">Total Samples</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary-card" id="summary-duration">
|
||||
<div class="summary-icon">⏱</div>
|
||||
<div class="summary-data">
|
||||
<div class="summary-value" id="stat-duration">--</div>
|
||||
<div class="summary-label">Duration</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary-card" id="summary-rate">
|
||||
<div class="summary-icon">⚡</div>
|
||||
<div class="summary-data">
|
||||
<div class="summary-value" id="stat-sample-rate">--</div>
|
||||
<div class="summary-label">Samples/sec</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary-card" id="summary-functions">
|
||||
<div class="summary-icon">λ</div>
|
||||
<div class="summary-data">
|
||||
<div class="summary-value" id="stat-functions">--</div>
|
||||
<div class="summary-label">Functions</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Efficiency Bar -->
|
||||
<div class="efficiency-section" id="efficiency-section" style="display: none;">
|
||||
<div class="efficiency-header">
|
||||
<span class="efficiency-label">Sampling Efficiency</span>
|
||||
<span class="efficiency-value" id="stat-efficiency">--</span>
|
||||
</div>
|
||||
<div class="efficiency-bar">
|
||||
<div class="efficiency-fill" id="efficiency-fill"></div>
|
||||
</div>
|
||||
<div class="missed-samples-header">
|
||||
<span class="efficiency-label">Missed samples</span>
|
||||
<span class="efficiency-value" id="stat-missed-samples">--</span>
|
||||
</div>
|
||||
<div class="efficiency-bar">
|
||||
<div class="efficiency-fill" id="missed-samples-fill"></div>
|
||||
</div>
|
||||
|
||||
<div class="stats-section">
|
||||
<!-- Hot Spots -->
|
||||
<div class="stats-container">
|
||||
<div class="stat-card hotspot-card">
|
||||
<div class="stat-icon">🥇</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-label">#1 Hot Spot</div>
|
||||
<div class="stat-file" id="hotspot-file-1">--</div>
|
||||
<div class="stat-value" id="hotspot-func-1">--</div>
|
||||
<div class="stat-detail" id="hotspot-detail-1">--</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card hotspot-card">
|
||||
<div class="stat-icon">🥈</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-label">#2 Hot Spot</div>
|
||||
<div class="stat-file" id="hotspot-file-2">--</div>
|
||||
<div class="stat-value" id="hotspot-func-2">--</div>
|
||||
<div class="stat-detail" id="hotspot-detail-2">--</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card hotspot-card">
|
||||
<div class="stat-icon">🥉</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-label">#3 Hot Spot</div>
|
||||
<div class="stat-file" id="hotspot-file-3">--</div>
|
||||
<div class="stat-value" id="hotspot-func-3">--</div>
|
||||
<div class="stat-detail" id="hotspot-detail-3">--</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="controls">
|
||||
<div class="controls-content">
|
||||
<button onclick="resetZoom()">🏠 Reset Zoom</button>
|
||||
<button onclick="exportSVG()" class="secondary">📁 Export SVG</button>
|
||||
<button onclick="toggleLegend()">🔥 Heat Map Legend</button>
|
||||
<div class="thread-filter-wrapper">
|
||||
<label class="thread-filter-label">🧵 Thread:</label>
|
||||
<select id="thread-filter" class="thread-filter-select" onchange="filterByThread()">
|
||||
<!-- Thread Stats Section (GIL/GC) -->
|
||||
<section class="sidebar-section thread-stats-section collapsible" id="thread-stats-bar" style="display: none;">
|
||||
<button class="section-header" onclick="toggleSection('thread-stats-bar')">
|
||||
<h3 class="section-title">Runtime Stats</h3>
|
||||
<svg class="section-chevron" width="12" height="12" viewBox="0 0 12 12" fill="none">
|
||||
<path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="section-content">
|
||||
<div class="stats-grid">
|
||||
<div class="stat-tile stat-tile--green" id="gil-held-stat">
|
||||
<div class="stat-tile-value" id="gil-held-pct">--</div>
|
||||
<div class="stat-tile-label">GIL Held</div>
|
||||
</div>
|
||||
<div class="stat-tile stat-tile--red" id="gil-released-stat">
|
||||
<div class="stat-tile-value" id="gil-released-pct">--</div>
|
||||
<div class="stat-tile-label">GIL Released</div>
|
||||
</div>
|
||||
<div class="stat-tile stat-tile--yellow" id="gil-waiting-stat">
|
||||
<div class="stat-tile-value" id="gil-waiting-pct">--</div>
|
||||
<div class="stat-tile-label">Waiting</div>
|
||||
</div>
|
||||
<div class="stat-tile stat-tile--purple" id="gc-stat">
|
||||
<div class="stat-tile-value" id="gc-pct">--</div>
|
||||
<div class="stat-tile-label">GC</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Hotspots Section -->
|
||||
<section class="sidebar-section collapsible" id="hotspots-section">
|
||||
<button class="section-header" onclick="toggleSection('hotspots-section')">
|
||||
<h3 class="section-title">Hotspots</h3>
|
||||
<svg class="section-chevron" width="12" height="12" viewBox="0 0 12 12" fill="none">
|
||||
<path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="section-content">
|
||||
<div class="hotspot" id="hotspot-1">
|
||||
<div class="hotspot-rank hotspot-rank--1">1</div>
|
||||
<div class="hotspot-info">
|
||||
<div class="hotspot-func" id="hotspot-func-1">--</div>
|
||||
<div class="hotspot-file" id="hotspot-file-1">--</div>
|
||||
<div class="hotspot-stats">
|
||||
<span class="hotspot-percent" id="hotspot-percent-1">--</span>
|
||||
<span id="hotspot-samples-1"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hotspot" id="hotspot-2">
|
||||
<div class="hotspot-rank hotspot-rank--2">2</div>
|
||||
<div class="hotspot-info">
|
||||
<div class="hotspot-func" id="hotspot-func-2">--</div>
|
||||
<div class="hotspot-file" id="hotspot-file-2">--</div>
|
||||
<div class="hotspot-stats">
|
||||
<span class="hotspot-percent" id="hotspot-percent-2">--</span>
|
||||
<span id="hotspot-samples-2"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hotspot" id="hotspot-3">
|
||||
<div class="hotspot-rank hotspot-rank--3">3</div>
|
||||
<div class="hotspot-info">
|
||||
<div class="hotspot-func" id="hotspot-func-3">--</div>
|
||||
<div class="hotspot-file" id="hotspot-file-3">--</div>
|
||||
<div class="hotspot-stats">
|
||||
<span class="hotspot-percent" id="hotspot-percent-3">--</span>
|
||||
<span id="hotspot-samples-3"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Thread Filter Section -->
|
||||
<section class="sidebar-section filter-section" id="thread-section" style="display: none;">
|
||||
<label class="filter-label" for="thread-filter">Thread Filter</label>
|
||||
<select
|
||||
id="thread-filter"
|
||||
class="filter-select"
|
||||
onchange="filterByThread()"
|
||||
>
|
||||
<option value="all">All Threads</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<button id="show-info-btn" title="Show navigation guide">ℹ</button>
|
||||
<!-- Legend Section -->
|
||||
<section class="sidebar-section legend-section collapsible" id="legend-section">
|
||||
<button class="section-header" onclick="toggleSection('legend-section')">
|
||||
<h3 class="section-title">Heat Map</h3>
|
||||
<svg class="section-chevron" width="12" height="12" viewBox="0 0 12 12" fill="none">
|
||||
<path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="section-content">
|
||||
<div class="legend">
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background: var(--heat-8)"></div>
|
||||
<span class="legend-label">Hottest</span>
|
||||
<span class="legend-range">≥60%</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background: var(--heat-7)"></div>
|
||||
<span class="legend-label">Very Hot</span>
|
||||
<span class="legend-range">35-60%</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background: var(--heat-6)"></div>
|
||||
<span class="legend-label">Hot</span>
|
||||
<span class="legend-range">18-35%</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background: var(--heat-5)"></div>
|
||||
<span class="legend-label">Warm</span>
|
||||
<span class="legend-range">12-18%</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background: var(--heat-4)"></div>
|
||||
<span class="legend-label">Medium</span>
|
||||
<span class="legend-range">6-12%</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background: var(--heat-3)"></div>
|
||||
<span class="legend-label">Cool</span>
|
||||
<span class="legend-range">3-6%</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background: var(--heat-2)"></div>
|
||||
<span class="legend-label">Cold</span>
|
||||
<span class="legend-range">1-3%</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background: var(--heat-1)"></div>
|
||||
<span class="legend-label">Coldest</span>
|
||||
<span class="legend-range"><1%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="info-panel" id="info-panel">
|
||||
<button id="close-info-btn" title="Close">×</button>
|
||||
<h3>Navigation Guide</h3>
|
||||
<p><strong>Click:</strong> Zoom into function</p>
|
||||
<p><strong>Hover:</strong> Show detailed information</p>
|
||||
<p><strong>Width:</strong> Time spent in function</p>
|
||||
<p><strong>Height:</strong> Call stack depth</p>
|
||||
<p><strong>Color:</strong> Performance intensity</p>
|
||||
</div>
|
||||
|
||||
<div class="legend-panel" id="legend-panel">
|
||||
<h3>🔥 Performance Heat Map</h3>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background-color: #3776ab"></div>
|
||||
<div>
|
||||
<div class="legend-label">Hottest Functions (≥60%)</div>
|
||||
<div class="legend-description">Highest performance impact</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background-color: #4584bb"></div>
|
||||
<div>
|
||||
<div class="legend-label">Very Hot Functions (35-60%)</div>
|
||||
<div class="legend-description">High performance impact</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background-color: #5592cc"></div>
|
||||
<div>
|
||||
<div class="legend-label">Hot Functions (18-35%)</div>
|
||||
<div class="legend-description">Notable performance cost</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background-color: #ffd43b"></div>
|
||||
<div>
|
||||
<div class="legend-label">Warm Functions (12-18%)</div>
|
||||
<div class="legend-description">Moderate impact</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background-color: #ffdc5c"></div>
|
||||
<div>
|
||||
<div class="legend-label">Medium Functions (6-12%)</div>
|
||||
<div class="legend-description">Some performance impact</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background-color: #ffe47d"></div>
|
||||
<div>
|
||||
<div class="legend-label">Cool Functions (3-6%)</div>
|
||||
<div class="legend-description">Low performance impact</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background-color: #ffec9e"></div>
|
||||
<div>
|
||||
<div class="legend-label">Cold Functions (1-3%)</div>
|
||||
<div class="legend-description">Minimal performance impact</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background-color: #fff4bf"></div>
|
||||
<div>
|
||||
<div class="legend-label">Coldest Functions (<1%)</div>
|
||||
<div class="legend-description">Very low performance impact</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-container">
|
||||
<!-- Chart Area -->
|
||||
<main class="chart-area">
|
||||
<div id="chart"></div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Status Bar -->
|
||||
<footer class="status-bar">
|
||||
<span class="status-item" id="status-location" style="display: none;">
|
||||
<span class="status-label">File:</span>
|
||||
<span class="status-value" id="status-file">--</span>
|
||||
</span>
|
||||
<span class="status-item" id="status-func-item" style="display: none;">
|
||||
<span class="status-label">Func:</span>
|
||||
<span class="status-value" id="status-func">--</span>
|
||||
</span>
|
||||
<span class="status-item" id="status-time-item" style="display: none;">
|
||||
<span class="status-label">Time:</span>
|
||||
<span class="status-value" id="status-time">--</span>
|
||||
</span>
|
||||
<span class="status-item" id="status-percent-item" style="display: none;">
|
||||
<span class="status-value accent" id="status-percent">--</span>
|
||||
</span>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- INLINE_JS -->
|
||||
|
|
|
|||
|
|
@ -112,8 +112,10 @@ def sample(self, collector, duration_sec=10):
|
|||
if self.realtime_stats and len(self.sample_intervals) > 0:
|
||||
print() # Add newline after real-time stats
|
||||
|
||||
sample_rate = num_samples / running_time
|
||||
sample_rate = num_samples / running_time if running_time > 0 else 0
|
||||
error_rate = (errors / num_samples) * 100 if num_samples > 0 else 0
|
||||
expected_samples = int(duration_sec / sample_interval_sec)
|
||||
missed_samples = (expected_samples - num_samples) / expected_samples * 100 if expected_samples > 0 else 0
|
||||
|
||||
# Don't print stats for live mode (curses is handling display)
|
||||
is_live_mode = LiveStatsCollector is not None and isinstance(collector, LiveStatsCollector)
|
||||
|
|
@ -124,9 +126,8 @@ def sample(self, collector, duration_sec=10):
|
|||
|
||||
# Pass stats to flamegraph collector if it's the right type
|
||||
if hasattr(collector, 'set_stats'):
|
||||
collector.set_stats(self.sample_interval_usec, running_time, sample_rate, error_rate, mode=self.mode)
|
||||
collector.set_stats(self.sample_interval_usec, running_time, sample_rate, error_rate, missed_samples, mode=self.mode)
|
||||
|
||||
expected_samples = int(duration_sec / sample_interval_sec)
|
||||
if num_samples < expected_samples and not is_live_mode and not interrupted:
|
||||
print(
|
||||
f"Warning: missed {expected_samples - num_samples} samples "
|
||||
|
|
|
|||
|
|
@ -113,13 +113,15 @@ def collect(self, stack_frames, skip_idle=False):
|
|||
# Call parent collect to process frames
|
||||
super().collect(stack_frames, skip_idle=skip_idle)
|
||||
|
||||
def set_stats(self, sample_interval_usec, duration_sec, sample_rate, error_rate=None, mode=None):
|
||||
def set_stats(self, sample_interval_usec, duration_sec, sample_rate,
|
||||
error_rate=None, missed_samples=None, mode=None):
|
||||
"""Set profiling statistics to include in flamegraph data."""
|
||||
self.stats = {
|
||||
"sample_interval_usec": sample_interval_usec,
|
||||
"duration_sec": duration_sec,
|
||||
"sample_rate": sample_rate,
|
||||
"error_rate": error_rate,
|
||||
"missed_samples": missed_samples,
|
||||
"mode": mode
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -494,7 +494,7 @@ def test_flamegraph_collector_export(self):
|
|||
# Should be valid HTML
|
||||
self.assertIn("<!doctype html>", content.lower())
|
||||
self.assertIn("<html", content)
|
||||
self.assertIn("Python Performance Flamegraph", content)
|
||||
self.assertIn("Tachyon Profiler - Flamegraph", content)
|
||||
self.assertIn("d3-flame-graph", content)
|
||||
|
||||
# Should contain the data
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue