Skip to content

Commit cdebad2

Browse files
authored
Merge branch 'main' into fix-shutil-recursion-loop
2 parents 0013914 + eb89286 commit cdebad2

8 files changed

Lines changed: 1987 additions & 1009 deletions

File tree

Lib/profiling/sampling/flamegraph.css

Lines changed: 999 additions & 447 deletions
Large diffs are not rendered by default.

Lib/profiling/sampling/flamegraph.js

Lines changed: 682 additions & 405 deletions
Large diffs are not rendered by default.

Lib/profiling/sampling/flamegraph_template.html

Lines changed: 283 additions & 151 deletions
Large diffs are not rendered by default.

Lib/profiling/sampling/sample.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,10 @@ def sample(self, collector, duration_sec=10):
112112
if self.realtime_stats and len(self.sample_intervals) > 0:
113113
print() # Add newline after real-time stats
114114

115-
sample_rate = num_samples / running_time
115+
sample_rate = num_samples / running_time if running_time > 0 else 0
116116
error_rate = (errors / num_samples) * 100 if num_samples > 0 else 0
117+
expected_samples = int(duration_sec / sample_interval_sec)
118+
missed_samples = (expected_samples - num_samples) / expected_samples * 100 if expected_samples > 0 else 0
117119

118120
# Don't print stats for live mode (curses is handling display)
119121
is_live_mode = LiveStatsCollector is not None and isinstance(collector, LiveStatsCollector)
@@ -124,9 +126,8 @@ def sample(self, collector, duration_sec=10):
124126

125127
# Pass stats to flamegraph collector if it's the right type
126128
if hasattr(collector, 'set_stats'):
127-
collector.set_stats(self.sample_interval_usec, running_time, sample_rate, error_rate, mode=self.mode)
129+
collector.set_stats(self.sample_interval_usec, running_time, sample_rate, error_rate, missed_samples, mode=self.mode)
128130

129-
expected_samples = int(duration_sec / sample_interval_sec)
130131
if num_samples < expected_samples and not is_live_mode and not interrupted:
131132
print(
132133
f"Warning: missed {expected_samples - num_samples} samples "

Lib/profiling/sampling/stack_collector.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,15 @@ def collect(self, stack_frames, skip_idle=False):
113113
# Call parent collect to process frames
114114
super().collect(stack_frames, skip_idle=skip_idle)
115115

116-
def set_stats(self, sample_interval_usec, duration_sec, sample_rate, error_rate=None, mode=None):
116+
def set_stats(self, sample_interval_usec, duration_sec, sample_rate,
117+
error_rate=None, missed_samples=None, mode=None):
117118
"""Set profiling statistics to include in flamegraph data."""
118119
self.stats = {
119120
"sample_interval_usec": sample_interval_usec,
120121
"duration_sec": duration_sec,
121122
"sample_rate": sample_rate,
122123
"error_rate": error_rate,
124+
"missed_samples": missed_samples,
123125
"mode": mode
124126
}
125127

Lib/test/test_profiling/test_sampling_profiler/test_collectors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ def test_flamegraph_collector_export(self):
494494
# Should be valid HTML
495495
self.assertIn("<!doctype html>", content.lower())
496496
self.assertIn("<html", content)
497-
self.assertIn("Python Performance Flamegraph", content)
497+
self.assertIn("Tachyon Profiler - Flamegraph", content)
498498
self.assertIn("d3-flame-graph", content)
499499

500500
# Should contain the data
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix quadratically increasing garbage collection delays in free-threaded
2+
build.

Python/gc_free_threading.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2210,7 +2210,19 @@ record_deallocation(PyThreadState *tstate)
22102210
gc->alloc_count--;
22112211
if (gc->alloc_count <= -LOCAL_ALLOC_COUNT_THRESHOLD) {
22122212
GCState *gcstate = &tstate->interp->gc;
2213-
_Py_atomic_add_int(&gcstate->young.count, (int)gc->alloc_count);
2213+
int count = _Py_atomic_load_int_relaxed(&gcstate->young.count);
2214+
int new_count;
2215+
do {
2216+
if (count == 0) {
2217+
break;
2218+
}
2219+
new_count = count + (int)gc->alloc_count;
2220+
if (new_count < 0) {
2221+
new_count = 0;
2222+
}
2223+
} while (!_Py_atomic_compare_exchange_int(&gcstate->young.count,
2224+
&count,
2225+
new_count));
22142226
gc->alloc_count = 0;
22152227
}
22162228
}

0 commit comments

Comments
 (0)