Skip to content

Commit c8ebaee

Browse files
authored
Merge branch 'main' into removetypecheck
2 parents 61543b3 + bc7b511 commit c8ebaee

21 files changed

+1880
-57
lines changed

Include/internal/pycore_global_objects_fini_generated.h

Lines changed: 768 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_runtime_init_generated.h

Lines changed: 768 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_runtime_structs.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ struct _Py_cached_objects {
106106
};
107107

108108
// These would be in pycore_long.h if it weren't for an include cycle.
109-
#define _PY_NSMALLPOSINTS 257
109+
#define _PY_NSMALLPOSINTS 1025
110110
#define _PY_NSMALLNEGINTS 5
111111

112112
#include "pycore_global_strings.h" // struct _Py_global_strings

Lib/inspect.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3397,7 +3397,7 @@ def _main():
33973397
else:
33983398
print('Line: {}'.format(lineno))
33993399

3400-
print('\n')
3400+
print()
34013401
else:
34023402
print(getsource(obj))
34033403

Lib/profiling/sampling/collector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,4 @@ def _iter_all_frames(self, stack_frames, skip_idle=False):
3030
continue
3131
frames = thread_info.frame_info
3232
if frames:
33-
yield frames
33+
yield frames, thread_info.thread_id

Lib/profiling/sampling/flamegraph.css

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,65 @@ body {
227227
background: #ffcd02;
228228
}
229229

230+
.thread-filter-wrapper {
231+
display: none;
232+
align-items: center;
233+
margin-left: 16px;
234+
background: white;
235+
border-radius: 6px;
236+
padding: 4px 8px 4px 12px;
237+
border: 2px solid #3776ab;
238+
transition: all 0.2s ease;
239+
}
240+
241+
.thread-filter-wrapper:hover {
242+
border-color: #2d5aa0;
243+
box-shadow: 0 2px 6px rgba(55, 118, 171, 0.2);
244+
}
245+
246+
.thread-filter-label {
247+
color: #3776ab;
248+
font-size: 14px;
249+
font-weight: 600;
250+
margin-right: 8px;
251+
display: flex;
252+
align-items: center;
253+
}
254+
255+
.thread-filter-select {
256+
background: transparent;
257+
color: #2e3338;
258+
border: none;
259+
padding: 4px 24px 4px 4px;
260+
font-size: 14px;
261+
font-weight: 600;
262+
cursor: pointer;
263+
min-width: 120px;
264+
font-family: inherit;
265+
appearance: none;
266+
-webkit-appearance: none;
267+
-moz-appearance: none;
268+
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%233776ab' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
269+
background-repeat: no-repeat;
270+
background-position: right 4px center;
271+
background-size: 16px;
272+
}
273+
274+
.thread-filter-select:focus {
275+
outline: none;
276+
}
277+
278+
.thread-filter-select:hover {
279+
color: #3776ab;
280+
}
281+
282+
.thread-filter-select option {
283+
padding: 8px;
284+
background: white;
285+
color: #2e3338;
286+
font-weight: normal;
287+
}
288+
230289
#chart {
231290
width: 100%;
232291
height: calc(100vh - 160px);

Lib/profiling/sampling/flamegraph.js

Lines changed: 132 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ const EMBEDDED_DATA = {{FLAMEGRAPH_DATA}};
22

33
// Global string table for resolving string indices
44
let stringTable = [];
5+
let originalData = null;
6+
let currentThreadFilter = 'all';
57

68
// Function to resolve string indices to actual strings
79
function resolveString(index) {
@@ -374,6 +376,12 @@ function initFlamegraph() {
374376
processedData = resolveStringIndices(EMBEDDED_DATA);
375377
}
376378

379+
// Store original data for filtering
380+
originalData = processedData;
381+
382+
// Initialize thread filter dropdown
383+
initThreadFilter(processedData);
384+
377385
const tooltip = createPythonTooltip(processedData);
378386
const chart = createFlamegraph(tooltip, processedData.value);
379387
renderFlamegraph(chart, processedData);
@@ -395,10 +403,26 @@ function populateStats(data) {
395403
const functionMap = new Map();
396404

397405
function collectFunctions(node) {
398-
const filename = resolveString(node.filename);
399-
const funcname = resolveString(node.funcname);
406+
if (!node) return;
407+
408+
let filename = typeof node.filename === 'number' ? resolveString(node.filename) : node.filename;
409+
let funcname = typeof node.funcname === 'number' ? resolveString(node.funcname) : node.funcname;
410+
411+
if (!filename || !funcname) {
412+
const nameStr = typeof node.name === 'number' ? resolveString(node.name) : node.name;
413+
if (nameStr?.includes('(')) {
414+
const match = nameStr.match(/^(.+?)\s*\((.+?):(\d+)\)$/);
415+
if (match) {
416+
funcname = funcname || match[1];
417+
filename = filename || match[2];
418+
}
419+
}
420+
}
400421

401-
if (filename && funcname) {
422+
filename = filename || 'unknown';
423+
funcname = funcname || 'unknown';
424+
425+
if (filename !== 'unknown' && funcname !== 'unknown' && node.value > 0) {
402426
// Calculate direct samples (this node's value minus children's values)
403427
let childrenValue = 0;
404428
if (node.children) {
@@ -447,15 +471,17 @@ function populateStats(data) {
447471
// Populate the 3 cards
448472
for (let i = 0; i < 3; i++) {
449473
const num = i + 1;
450-
if (i < hotSpots.length) {
474+
if (i < hotSpots.length && hotSpots[i]) {
451475
const hotspot = hotSpots[i];
452-
const basename = hotspot.filename.split('/').pop();
453-
let funcDisplay = hotspot.funcname;
476+
const filename = hotspot.filename || 'unknown';
477+
const basename = filename !== 'unknown' ? filename.split('/').pop() : 'unknown';
478+
const lineno = hotspot.lineno ?? '?';
479+
let funcDisplay = hotspot.funcname || 'unknown';
454480
if (funcDisplay.length > 35) {
455481
funcDisplay = funcDisplay.substring(0, 32) + '...';
456482
}
457483

458-
document.getElementById(`hotspot-file-${num}`).textContent = `${basename}:${hotspot.lineno}`;
484+
document.getElementById(`hotspot-file-${num}`).textContent = `${basename}:${lineno}`;
459485
document.getElementById(`hotspot-func-${num}`).textContent = funcDisplay;
460486
document.getElementById(`hotspot-detail-${num}`).textContent = `${hotspot.directPercent.toFixed(1)}% samples (${hotspot.directSamples.toLocaleString()})`;
461487
} else {
@@ -505,3 +531,102 @@ function clearSearch() {
505531
}
506532
}
507533

534+
function initThreadFilter(data) {
535+
const threadFilter = document.getElementById('thread-filter');
536+
const threadWrapper = document.querySelector('.thread-filter-wrapper');
537+
538+
if (!threadFilter || !data.threads) {
539+
return;
540+
}
541+
542+
// Clear existing options except "All Threads"
543+
threadFilter.innerHTML = '<option value="all">All Threads</option>';
544+
545+
// Add thread options
546+
const threads = data.threads || [];
547+
threads.forEach(threadId => {
548+
const option = document.createElement('option');
549+
option.value = threadId;
550+
option.textContent = `Thread ${threadId}`;
551+
threadFilter.appendChild(option);
552+
});
553+
554+
// Show filter if more than one thread
555+
if (threads.length > 1 && threadWrapper) {
556+
threadWrapper.style.display = 'inline-flex';
557+
}
558+
}
559+
560+
function filterByThread() {
561+
const threadFilter = document.getElementById('thread-filter');
562+
if (!threadFilter || !originalData) return;
563+
564+
const selectedThread = threadFilter.value;
565+
currentThreadFilter = selectedThread;
566+
567+
let filteredData;
568+
if (selectedThread === 'all') {
569+
// Show all data
570+
filteredData = originalData;
571+
} else {
572+
// Filter data by thread
573+
const threadId = parseInt(selectedThread);
574+
filteredData = filterDataByThread(originalData, threadId);
575+
576+
if (filteredData.strings) {
577+
stringTable = filteredData.strings;
578+
filteredData = resolveStringIndices(filteredData);
579+
}
580+
}
581+
582+
// Re-render flamegraph with filtered data
583+
const tooltip = createPythonTooltip(filteredData);
584+
const chart = createFlamegraph(tooltip, filteredData.value);
585+
renderFlamegraph(chart, filteredData);
586+
}
587+
588+
function filterDataByThread(data, threadId) {
589+
function filterNode(node) {
590+
if (!node.threads || !node.threads.includes(threadId)) {
591+
return null;
592+
}
593+
594+
const filteredNode = {
595+
...node,
596+
children: []
597+
};
598+
599+
if (node.children && Array.isArray(node.children)) {
600+
filteredNode.children = node.children
601+
.map(child => filterNode(child))
602+
.filter(child => child !== null);
603+
}
604+
605+
return filteredNode;
606+
}
607+
608+
const filteredRoot = {
609+
...data,
610+
children: []
611+
};
612+
613+
if (data.children && Array.isArray(data.children)) {
614+
filteredRoot.children = data.children
615+
.map(child => filterNode(child))
616+
.filter(child => child !== null);
617+
}
618+
619+
function recalculateValue(node) {
620+
if (!node.children || node.children.length === 0) {
621+
return node.value || 0;
622+
}
623+
const childrenValue = node.children.reduce((sum, child) => sum + recalculateValue(child), 0);
624+
node.value = Math.max(node.value || 0, childrenValue);
625+
return node.value;
626+
}
627+
628+
recalculateValue(filteredRoot);
629+
630+
return filteredRoot;
631+
}
632+

Lib/profiling/sampling/flamegraph_template.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ <h1>Tachyon Profiler Performance Flamegraph</h1>
6363
<button onclick="resetZoom()">🏠 Reset Zoom</button>
6464
<button onclick="exportSVG()" class="secondary">📁 Export SVG</button>
6565
<button onclick="toggleLegend()">🔥 Heat Map Legend</button>
66+
<div class="thread-filter-wrapper">
67+
<label class="thread-filter-label">🧵 Thread:</label>
68+
<select id="thread-filter" class="thread-filter-select" onchange="filterByThread()">
69+
<option value="all">All Threads</option>
70+
</select>
71+
</div>
6672
</div>
6773
</div>
6874

Lib/profiling/sampling/pstats_collector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def _process_frames(self, frames):
4141
self.callers[callee][caller] += 1
4242

4343
def collect(self, stack_frames):
44-
for frames in self._iter_all_frames(stack_frames, skip_idle=self.skip_idle):
44+
for frames, thread_id in self._iter_all_frames(stack_frames, skip_idle=self.skip_idle):
4545
self._process_frames(frames)
4646

4747
def export(self, filename):

Lib/profiling/sampling/sample.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,7 @@ def main():
754754
"--mode",
755755
choices=["wall", "cpu", "gil"],
756756
default="wall",
757-
help="Sampling mode: wall (all threads), cpu (only CPU-running threads), gil (only GIL-holding threads)",
757+
help="Sampling mode: wall (all threads), cpu (only CPU-running threads), gil (only GIL-holding threads) (default: wall)",
758758
)
759759

760760
# Output format selection

0 commit comments

Comments
 (0)