Skip to content

Commit fb48a88

Browse files
pablogsallkollar
authored andcommitted
fixup! fixup! Update license.rst
1 parent 4f10915 commit fb48a88

File tree

5 files changed

+332
-2
lines changed

5 files changed

+332
-2
lines changed

Lib/profiling/sampling/flamegraph.css

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,95 @@ body {
6161
font-weight: 300;
6262
}
6363

64+
.stats-section {
65+
background: #ffffff;
66+
padding: 24px 0;
67+
border-bottom: 1px solid #e9ecef;
68+
}
69+
70+
.stats-container {
71+
max-width: 1200px;
72+
margin: 0 auto;
73+
padding: 0 24px;
74+
display: grid;
75+
grid-template-columns: repeat(3, 1fr);
76+
gap: 20px;
77+
}
78+
79+
.stat-card {
80+
background: #ffffff;
81+
border: 1px solid #e9ecef;
82+
border-radius: 12px;
83+
padding: 20px;
84+
display: flex;
85+
align-items: flex-start;
86+
gap: 16px;
87+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
88+
transition: all 0.2s ease;
89+
min-height: 120px;
90+
}
91+
92+
.stat-card:hover {
93+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
94+
transform: translateY(-2px);
95+
}
96+
97+
.stat-icon {
98+
font-size: 32px;
99+
width: 56px;
100+
height: 56px;
101+
display: flex;
102+
align-items: center;
103+
justify-content: center;
104+
background: linear-gradient(135deg, #3776ab 0%, #4584bb 100%);
105+
border-radius: 50%;
106+
flex-shrink: 0;
107+
box-shadow: 0 2px 8px rgba(55, 118, 171, 0.3);
108+
}
109+
110+
.stat-content {
111+
flex: 1;
112+
}
113+
114+
.stat-label {
115+
font-size: 14px;
116+
color: #5a6c7d;
117+
font-weight: 500;
118+
margin-bottom: 4px;
119+
text-transform: uppercase;
120+
letter-spacing: 0.5px;
121+
}
122+
123+
.stat-value {
124+
font-size: 16px;
125+
font-weight: 700;
126+
color: #2e3338;
127+
line-height: 1.3;
128+
margin-bottom: 4px;
129+
word-break: break-word;
130+
overflow-wrap: break-word;
131+
}
132+
133+
.stat-file {
134+
font-size: 12px;
135+
color: #8b949e;
136+
font-weight: 400;
137+
margin-bottom: 2px;
138+
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
139+
word-break: break-word;
140+
overflow-wrap: break-word;
141+
}
142+
143+
.stat-detail {
144+
font-size: 12px;
145+
color: #5a6c7d;
146+
font-weight: 400;
147+
line-height: 1.4;
148+
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
149+
word-break: break-word;
150+
overflow-wrap: break-word;
151+
}
152+
64153
.controls {
65154
background: #f8f9fa;
66155
border-bottom: 1px solid #e9ecef;
@@ -72,6 +161,34 @@ body {
72161
max-width: 1200px;
73162
margin: 0 auto;
74163
padding: 0 24px;
164+
text-align: center;
165+
}
166+
167+
.search-container {
168+
max-width: 1200px;
169+
margin: 16px auto 0 auto;
170+
padding: 0 24px;
171+
text-align: center;
172+
}
173+
174+
#search-input {
175+
padding: 12px 20px;
176+
border: 2px solid #e9ecef;
177+
border-radius: 6px;
178+
font-size: 14px;
179+
font-family: inherit;
180+
background: white;
181+
transition: all 0.2s ease;
182+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.06);
183+
display: inline-block;
184+
width: auto;
185+
min-width: 300px;
186+
}
187+
188+
#search-input:focus {
189+
outline: none;
190+
border-color: #3776ab;
191+
box-shadow: 0 4px 12px rgba(55, 118, 171, 0.2);
75192
}
76193

77194
.controls button {

Lib/profiling/sampling/flamegraph.js

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,9 @@ function main() {
211211

212212
// Make chart globally accessible for controls
213213
window.flamegraphChart = chart;
214+
215+
// Populate stats cards
216+
populateStats(data);
214217
}
215218

216219
// Wait for libraries to load
@@ -220,6 +223,8 @@ document.addEventListener("DOMContentLoaded", function () {
220223
const infoBtn = document.getElementById("show-info-btn");
221224
const infoPanel = document.getElementById("info-panel");
222225
const closeBtn = document.getElementById("close-info-btn");
226+
const searchInput = document.getElementById("search-input");
227+
223228
if (infoBtn && infoPanel) {
224229
infoBtn.addEventListener("click", function () {
225230
const isOpen = infoPanel.style.display === "block";
@@ -231,6 +236,72 @@ document.addEventListener("DOMContentLoaded", function () {
231236
infoPanel.style.display = "none";
232237
});
233238
}
239+
240+
// Add search functionality - wait for chart to be ready
241+
if (searchInput) {
242+
let searchTimeout;
243+
244+
function performSearch() {
245+
const searchTerm = searchInput.value.trim();
246+
247+
// Clear previous search highlighting
248+
d3.selectAll("#chart rect")
249+
.style("stroke", null)
250+
.style("stroke-width", null)
251+
.style("opacity", null);
252+
253+
if (searchTerm && searchTerm.length > 0) {
254+
// First, dim all rectangles
255+
d3.selectAll("#chart rect")
256+
.style("opacity", 0.3);
257+
258+
// Then highlight and restore opacity for matching nodes
259+
let matchCount = 0;
260+
d3.selectAll("#chart rect")
261+
.each(function(d) {
262+
if (d && d.data) {
263+
const name = d.data.name || "";
264+
const funcname = d.data.funcname || "";
265+
const filename = d.data.filename || "";
266+
267+
const matches = name.toLowerCase().includes(searchTerm.toLowerCase()) ||
268+
funcname.toLowerCase().includes(searchTerm.toLowerCase()) ||
269+
filename.toLowerCase().includes(searchTerm.toLowerCase());
270+
271+
if (matches) {
272+
matchCount++;
273+
d3.select(this)
274+
.style("opacity", 1)
275+
.style("stroke", "#ff6b35")
276+
.style("stroke-width", "2px")
277+
.style("stroke-dasharray", "3,3");
278+
}
279+
}
280+
});
281+
282+
// Update search input style based on results
283+
if (matchCount > 0) {
284+
searchInput.style.borderColor = "#28a745";
285+
searchInput.style.boxShadow = "0 0 0 3px rgba(40, 167, 69, 0.1)";
286+
} else {
287+
searchInput.style.borderColor = "#dc3545";
288+
searchInput.style.boxShadow = "0 0 0 3px rgba(220, 53, 69, 0.1)";
289+
}
290+
} else {
291+
// Reset search input style
292+
searchInput.style.borderColor = "#e9ecef";
293+
searchInput.style.boxShadow = "0 2px 4px rgba(0, 0, 0, 0.06)";
294+
}
295+
}
296+
297+
searchInput.addEventListener("input", function() {
298+
clearTimeout(searchTimeout);
299+
searchTimeout = setTimeout(performSearch, 150);
300+
});
301+
302+
// Make search function globally accessible
303+
window.performSearch = performSearch;
304+
}
234305
});
235306

236307
// Python color palette - cold to hot
@@ -245,6 +316,81 @@ const pythonColors = [
245316
"#3776ab", // Hottest - Python blue (≥60%)
246317
];
247318

319+
function populateStats(data) {
320+
const totalSamples = data.value || 0;
321+
322+
// Collect all functions with their metrics, aggregated by function name
323+
const functionMap = new Map();
324+
325+
function collectFunctions(node) {
326+
if (node.filename && node.funcname) {
327+
// Calculate direct samples (this node's value minus children's values)
328+
let childrenValue = 0;
329+
if (node.children) {
330+
childrenValue = node.children.reduce((sum, child) => sum + child.value, 0);
331+
}
332+
const directSamples = Math.max(0, node.value - childrenValue);
333+
334+
// Use file:line:funcname as key to ensure uniqueness
335+
const funcKey = `${node.filename}:${node.lineno || '?'}:${node.funcname}`;
336+
337+
if (functionMap.has(funcKey)) {
338+
const existing = functionMap.get(funcKey);
339+
existing.directSamples += directSamples;
340+
existing.directPercent = (existing.directSamples / totalSamples) * 100;
341+
// Keep the most representative file/line (the one with more samples)
342+
if (directSamples > existing.maxSingleSamples) {
343+
existing.filename = node.filename;
344+
existing.lineno = node.lineno || '?';
345+
existing.maxSingleSamples = directSamples;
346+
}
347+
} else {
348+
functionMap.set(funcKey, {
349+
filename: node.filename,
350+
lineno: node.lineno || '?',
351+
funcname: node.funcname,
352+
directSamples,
353+
directPercent: (directSamples / totalSamples) * 100,
354+
maxSingleSamples: directSamples
355+
});
356+
}
357+
}
358+
359+
if (node.children) {
360+
node.children.forEach(child => collectFunctions(child));
361+
}
362+
}
363+
364+
collectFunctions(data);
365+
366+
// Convert map to array and get top 3 hotspots
367+
const hotSpots = Array.from(functionMap.values())
368+
.filter(f => f.directPercent > 0.5) // At least 0.5% to be significant
369+
.sort((a, b) => b.directPercent - a.directPercent)
370+
.slice(0, 3);
371+
372+
// Populate the 3 cards
373+
for (let i = 0; i < 3; i++) {
374+
const num = i + 1;
375+
if (i < hotSpots.length) {
376+
const hotspot = hotSpots[i];
377+
const basename = hotspot.filename.split('/').pop();
378+
let funcDisplay = hotspot.funcname;
379+
if (funcDisplay.length > 35) {
380+
funcDisplay = funcDisplay.substring(0, 32) + '...';
381+
}
382+
383+
document.getElementById(`hotspot-file-${num}`).textContent = `${basename}:${hotspot.lineno}`;
384+
document.getElementById(`hotspot-func-${num}`).textContent = funcDisplay;
385+
document.getElementById(`hotspot-detail-${num}`).textContent = `${hotspot.directPercent.toFixed(1)}% samples (${hotspot.directSamples.toLocaleString()})`;
386+
} else {
387+
document.getElementById(`hotspot-file-${num}`).textContent = '--';
388+
document.getElementById(`hotspot-func-${num}`).textContent = '--';
389+
document.getElementById(`hotspot-detail-${num}`).textContent = '--';
390+
}
391+
}
392+
}
393+
248394
// Control functions
249395
function resetZoom() {
250396
if (window.flamegraphChart) {
@@ -274,6 +420,16 @@ function toggleLegend() {
274420
legendPanel.style.display = isHidden ? "block" : "none";
275421
}
276422

423+
function clearSearch() {
424+
const searchInput = document.getElementById("search-input");
425+
if (searchInput) {
426+
searchInput.value = "";
427+
if (window.flamegraphChart) {
428+
window.flamegraphChart.clear();
429+
}
430+
}
431+
}
432+
277433
// Handle window resize
278434
window.addEventListener("resize", function () {
279435
if (window.flamegraphChart && window.flamegraphData) {

Lib/profiling/sampling/flamegraph_template.html

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,47 @@ <h1>Python Performance Flamegraph</h1>
2323
</div>
2424
</div>
2525

26+
<div class="stats-section">
27+
<div class="stats-container">
28+
<div class="stat-card hotspot-card">
29+
<div class="stat-icon">🥇</div>
30+
<div class="stat-content">
31+
<div class="stat-label">#1 Hot Spot</div>
32+
<div class="stat-file" id="hotspot-file-1">--</div>
33+
<div class="stat-value" id="hotspot-func-1">--</div>
34+
<div class="stat-detail" id="hotspot-detail-1">--</div>
35+
</div>
36+
</div>
37+
<div class="stat-card hotspot-card">
38+
<div class="stat-icon">🥈</div>
39+
<div class="stat-content">
40+
<div class="stat-label">#2 Hot Spot</div>
41+
<div class="stat-file" id="hotspot-file-2">--</div>
42+
<div class="stat-value" id="hotspot-func-2">--</div>
43+
<div class="stat-detail" id="hotspot-detail-2">--</div>
44+
</div>
45+
</div>
46+
<div class="stat-card hotspot-card">
47+
<div class="stat-icon">🥉</div>
48+
<div class="stat-content">
49+
<div class="stat-label">#3 Hot Spot</div>
50+
<div class="stat-file" id="hotspot-file-3">--</div>
51+
<div class="stat-value" id="hotspot-func-3">--</div>
52+
<div class="stat-detail" id="hotspot-detail-3">--</div>
53+
</div>
54+
</div>
55+
</div>
56+
</div>
57+
2658
<div class="controls">
2759
<div class="controls-content">
2860
<button onclick="resetZoom()">🏠 Reset Zoom</button>
2961
<button onclick="exportSVG()" class="secondary">📁 Export SVG</button>
3062
<button onclick="toggleLegend()">🔥 Heat Map Legend</button>
3163
</div>
64+
<div class="search-container">
65+
<input type="text" id="search-input" placeholder="🔍 Search functions..." />
66+
</div>
3267
</div>
3368

3469
<button id="show-info-btn" title="Show navigation guide">&#8505;</button>

Lib/profiling/sampling/sample.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,16 @@ def sample(self, collector, duration_sec=10):
191191
if self.realtime_stats and len(self.sample_intervals) > 0:
192192
print() # Add newline after real-time stats
193193

194+
sample_rate = num_samples / running_time
195+
error_rate = (errors / num_samples) * 100 if num_samples > 0 else 0
196+
194197
print(f"Captured {num_samples} samples in {running_time:.2f} seconds")
195-
print(f"Sample rate: {num_samples / running_time:.2f} samples/sec")
196-
print(f"Error rate: {(errors / num_samples) * 100:.2f}%")
198+
print(f"Sample rate: {sample_rate:.2f} samples/sec")
199+
print(f"Error rate: {error_rate:.2f}%")
200+
201+
# Pass stats to flamegraph collector if it's the right type
202+
if hasattr(collector, 'set_stats'):
203+
collector.set_stats(self.sample_interval_usec, running_time, sample_rate, error_rate)
197204

198205
expected_samples = int(duration_sec / sample_interval_sec)
199206
if num_samples < expected_samples:

0 commit comments

Comments
 (0)