Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added Lib/data.bin
Binary file not shown.
78 changes: 39 additions & 39 deletions Lib/profiling/sampling/live_collector/collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,20 +137,20 @@ def __init__(
self._saved_stderr = None
self._devnull = None
self._last_display_update = None
self._max_sample_rate = 0 # Track maximum sample rate seen
self._successful_samples = 0 # Track samples that captured frames
self._failed_samples = 0 # Track samples that failed to capture frames
self._display_update_interval = DISPLAY_UPDATE_INTERVAL # Instance variable for display refresh rate
self.max_sample_rate = 0 # Track maximum sample rate seen
self.successful_samples = 0 # Track samples that captured frames
self.failed_samples = 0 # Track samples that failed to capture frames
self.display_update_interval = DISPLAY_UPDATE_INTERVAL # Instance variable for display refresh rate

# Thread status statistics (bit flags)
self._thread_status_counts = {
self.thread_status_counts = {
"has_gil": 0,
"on_cpu": 0,
"gil_requested": 0,
"unknown": 0,
"total": 0, # Total thread count across all samples
}
self._gc_frame_samples = 0 # Track samples with GC frames
self.gc_frame_samples = 0 # Track samples with GC frames

# Interactive controls state
self.paused = False # Pause UI updates (profiling continues)
Expand All @@ -174,10 +174,10 @@ def __init__(
self._path_prefixes = self._get_common_path_prefixes()

# Widgets (initialized when display is available)
self._header_widget = None
self._table_widget = None
self._footer_widget = None
self._help_widget = None
self.header_widget = None
self.table_widget = None
self.footer_widget = None
self.help_widget = None

# Color mode
self._can_colorize = _colorize.can_colorize()
Expand Down Expand Up @@ -256,7 +256,7 @@ def _get_common_path_prefixes(self):

return prefixes

def _simplify_path(self, filepath):
def simplify_path(self, filepath):
"""Simplify a file path by removing common prefixes."""
# Try to match against known prefixes
for prefix_path in self._path_prefixes:
Expand All @@ -268,7 +268,7 @@ def _simplify_path(self, filepath):
# If no match, return the original path
return filepath

def _process_frames(self, frames, thread_id=None):
def process_frames(self, frames, thread_id=None):
"""Process a single thread's frame stack.

Args:
Expand All @@ -295,7 +295,7 @@ def _process_frames(self, frames, thread_id=None):
thread_data.result[top_location]["direct_calls"] += 1

def collect_failed_sample(self):
self._failed_samples += 1
self.failed_samples += 1
self.total_samples += 1

def collect(self, stack_frames):
Expand Down Expand Up @@ -349,7 +349,7 @@ def collect(self, stack_frames):

frames = getattr(thread_info, "frame_info", None)
if frames:
self._process_frames(frames, thread_id=thread_id)
self.process_frames(frames, thread_id=thread_id)

# Track thread IDs only for threads that actually have samples
if (
Expand All @@ -375,12 +375,12 @@ def collect(self, stack_frames):

# Update cumulative thread status counts
for key, count in temp_status_counts.items():
self._thread_status_counts[key] += count
self.thread_status_counts[key] += count

if has_gc_frame:
self._gc_frame_samples += 1
self.gc_frame_samples += 1

self._successful_samples += 1
self.successful_samples += 1
self.total_samples += 1

# Handle input on every sample for instant responsiveness
Expand All @@ -393,15 +393,15 @@ def collect(self, stack_frames):
if (
self._last_display_update is None
or (current_time - self._last_display_update)
>= self._display_update_interval
>= self.display_update_interval
):
self._update_display()
self._last_display_update = current_time

def _prepare_display_data(self, height):
"""Prepare data for display rendering."""
elapsed = self.elapsed_time
stats_list = self._build_stats_list()
stats_list = self.build_stats_list()

# Calculate available space for stats
# Add extra lines for finished banner when in finished state
Expand All @@ -422,15 +422,15 @@ def _prepare_display_data(self, height):

def _initialize_widgets(self, colors):
"""Initialize widgets with display and colors."""
if self._header_widget is None:
if self.header_widget is None:
# Initialize trend tracker with colors
if self._trend_tracker is None:
self._trend_tracker = TrendTracker(colors, enabled=True)

self._header_widget = HeaderWidget(self.display, colors, self)
self._table_widget = TableWidget(self.display, colors, self)
self._footer_widget = FooterWidget(self.display, colors, self)
self._help_widget = HelpWidget(self.display, colors)
self.header_widget = HeaderWidget(self.display, colors, self)
self.table_widget = TableWidget(self.display, colors, self)
self.footer_widget = FooterWidget(self.display, colors, self)
self.help_widget = HelpWidget(self.display, colors)

def _render_display_sections(
self, height, width, elapsed, stats_list, colors
Expand All @@ -442,12 +442,12 @@ def _render_display_sections(
self._initialize_widgets(colors)

# Render header
line = self._header_widget.render(
line = self.header_widget.render(
line, width, elapsed=elapsed, stats_list=stats_list
)

# Render table
line = self._table_widget.render(
line = self.table_widget.render(
line, width, height=height, stats_list=stats_list
)

Expand All @@ -473,7 +473,7 @@ def _update_display(self):

# Show help screen if requested
if self.show_help:
self._help_widget.render(0, width, height=height)
self.help_widget.render(0, width, height=height)
self.display.refresh()
return

Expand All @@ -486,11 +486,11 @@ def _update_display(self):
)

# Footer
self._footer_widget.render(height - 2, width)
self.footer_widget.render(height - 2, width)

# Show filter input prompt if in filter input mode
if self.filter_input_mode:
self._footer_widget.render_filter_input_prompt(
self.footer_widget.render_filter_input_prompt(
height - 1, width
)

Expand Down Expand Up @@ -616,7 +616,7 @@ def _setup_colors(self):
"trend_stable": A_NORMAL,
}

def _build_stats_list(self):
def build_stats_list(self):
"""Build and sort the statistics list."""
stats_list = []
result_source = self._get_current_result_source()
Expand Down Expand Up @@ -707,17 +707,17 @@ def reset_stats(self):
self.view_mode = "ALL"
self.current_thread_index = 0
self.total_samples = 0
self._successful_samples = 0
self._failed_samples = 0
self._max_sample_rate = 0
self._thread_status_counts = {
self.successful_samples = 0
self.failed_samples = 0
self.max_sample_rate = 0
self.thread_status_counts = {
"has_gil": 0,
"on_cpu": 0,
"gil_requested": 0,
"unknown": 0,
"total": 0,
}
self._gc_frame_samples = 0
self.gc_frame_samples = 0
# Clear trend tracking
if self._trend_tracker is not None:
self._trend_tracker.clear()
Expand Down Expand Up @@ -886,14 +886,14 @@ def _handle_input(self):

elif ch == ord("+") or ch == ord("="):
# Decrease update interval (faster refresh)
self._display_update_interval = max(
0.05, self._display_update_interval - 0.05
self.display_update_interval = max(
0.05, self.display_update_interval - 0.05
) # Min 20Hz

elif ch == ord("-") or ch == ord("_"):
# Increase update interval (slower refresh)
self._display_update_interval = min(
1.0, self._display_update_interval + 0.05
self.display_update_interval = min(
1.0, self.display_update_interval + 0.05
) # Max 1Hz

elif ch == ord("c") or ch == ord("C"):
Expand Down
18 changes: 9 additions & 9 deletions Lib/profiling/sampling/live_collector/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ def draw_header_info(self, line, width, elapsed):

# Calculate display refresh rate
refresh_hz = (
1.0 / self.collector._display_update_interval if self.collector._display_update_interval > 0 else 0
1.0 / self.collector.display_update_interval if self.collector.display_update_interval > 0 else 0
)

# Get current view mode and thread display
Expand Down Expand Up @@ -248,8 +248,8 @@ def draw_sample_stats(self, line, width, elapsed):
)

# Update max sample rate
if sample_rate > self.collector._max_sample_rate:
self.collector._max_sample_rate = sample_rate
if sample_rate > self.collector.max_sample_rate:
self.collector.max_sample_rate = sample_rate

col = 0
self.add_str(line, col, "Samples: ", curses.A_BOLD)
Expand Down Expand Up @@ -308,11 +308,11 @@ def draw_sample_stats(self, line, width, elapsed):
def draw_efficiency_bar(self, line, width):
"""Draw sample efficiency bar showing success/failure rates."""
success_pct = (
self.collector._successful_samples
self.collector.successful_samples
/ max(1, self.collector.total_samples)
) * 100
failed_pct = (
self.collector._failed_samples
self.collector.failed_samples
/ max(1, self.collector.total_samples)
) * 100

Expand All @@ -327,7 +327,7 @@ def draw_efficiency_bar(self, line, width):
bar_width = min(MAX_EFFICIENCY_BAR_WIDTH, available_width)
success_fill = int(
(
self.collector._successful_samples
self.collector.successful_samples
/ max(1, self.collector.total_samples)
)
* bar_width
Expand Down Expand Up @@ -381,7 +381,7 @@ def draw_thread_status(self, line, width):
"""Draw thread status statistics and GC information."""
# Get status counts for current view mode
thread_data = self.collector._get_current_thread_data()
status_counts = thread_data.as_status_dict() if thread_data else self.collector._thread_status_counts
status_counts = thread_data.as_status_dict() if thread_data else self.collector.thread_status_counts

# Calculate percentages
total_threads = max(1, status_counts["total"])
Expand All @@ -395,7 +395,7 @@ def draw_thread_status(self, line, width):
pct_gc = (thread_data.gc_frame_samples / total_samples) * 100
else:
total_samples = max(1, self.collector.total_samples)
pct_gc = (self.collector._gc_frame_samples / total_samples) * 100
pct_gc = (self.collector.gc_frame_samples / total_samples) * 100

col = 0
self.add_str(line, col, "Threads: ", curses.A_BOLD)
Expand Down Expand Up @@ -809,7 +809,7 @@ def get_trend_color(column_name):

# File:line column
if col < width - 10:
simplified_path = self.collector._simplify_path(filename)
simplified_path = self.collector.simplify_path(filename)
file_line = f"{simplified_path}:{lineno}"
remaining_width = width - col - 1
self.add_str(
Expand Down
Loading
Loading