Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
dd27e5e
Extend RemoteUnwinder to capture precise bytecode locations
pablogsal Dec 3, 2025
70f2ae0
Add opcode utilities and --opcodes CLI flag
pablogsal Dec 3, 2025
aedc000
Track opcode sample counts in flamegraph collector
pablogsal Dec 3, 2025
19ff11b
Emit opcode interval markers in Gecko collector
pablogsal Dec 3, 2025
af27d23
Add bytecode panel to heatmap visualization
pablogsal Dec 3, 2025
7ffe4cb
Add opcode panel to live profiler TUI
pablogsal Dec 3, 2025
8b423df
Update tests for location tuple and opcode field
pablogsal Dec 3, 2025
8129e3d
Merge remote-tracking branch 'upstream/main' into tachyon-opcodes
pablogsal Dec 7, 2025
965f521
Better test
pablogsal Dec 7, 2025
dac78a5
Merge remote-tracking branch 'upstream/main' into tachyon-opcodes
pablogsal Dec 7, 2025
12c02f6
Add news entry
pablogsal Dec 7, 2025
c10628a
CI fixes
pablogsal Dec 7, 2025
04563f0
CI fixes
pablogsal Dec 8, 2025
f368890
Fix C-API calls
pablogsal Dec 8, 2025
93f7abd
CSS fixes for classes and dark mode
savannahostrowski Dec 9, 2025
dc127bb
Merge pull request #110 from savannahostrowski/tachyon-opcodes-savannah
pablogsal Dec 9, 2025
43a298b
address review
pablogsal Dec 9, 2025
a4685fd
Merge branch 'main' into tachyon-opcodes
pablogsal Dec 9, 2025
b13b6f0
Docs
pablogsal Dec 9, 2025
50f63d0
Make bytecode spacer
pablogsal Dec 9, 2025
6eed927
Update Lib/profiling/sampling/_heatmap_assets/heatmap.js
pablogsal Dec 9, 2025
1c630ce
Update heatmap.css
pablogsal Dec 9, 2025
56e68c1
Tachyon Heatmap responsive styles
savannahostrowski Dec 10, 2025
e010870
Merge pull request #113 from savannahostrowski/heatmap-responsive
pablogsal Dec 10, 2025
ede0f79
Update Doc/library/profiling.sampling.rst
pablogsal Dec 10, 2025
f838c5d
Fix shift when selecting
StanFromIreland Dec 10, 2025
13ecd61
Merge pull request #114 from StanFromIreland/tachyon-opcodes-jump
pablogsal Dec 10, 2025
66610ff
Use any for stdlib line numbers
pablogsal Dec 10, 2025
aab1f3c
Update profiling.sampling.rst
pablogsal Dec 11, 2025
947f555
Docs
pablogsal Dec 11, 2025
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
Prev Previous commit
Next Next commit
address review
  • Loading branch information
pablogsal committed Dec 9, 2025
commit 43a298b512226570ca02329214a3fd7ce51dcd5d
2 changes: 1 addition & 1 deletion Lib/profiling/sampling/_flamegraph_assets/flamegraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ function createPythonTooltip(data) {
<div class="${nameClass}">
${opcodeInfo.opname}${baseOpHint}${specializedBadge}
</div>
<div class="tooltip-opcode-count">${count.toLocaleString()}</div>
<div class="tooltip-opcode-count">${count.toLocaleString()} (${pct}%)</div>
<div class="tooltip-opcode-bar">
<div class="tooltip-opcode-bar-fill" style="width: ${barWidth}%;"></div>
</div>
Expand Down
6 changes: 4 additions & 2 deletions Lib/profiling/sampling/_heatmap_assets/heatmap.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,10 +368,12 @@ function calculateHeatColor(intensity) {
const normalizedIntensity = (intensity - 0.3) / 0.7;
// Warm orange-red with increasing opacity for hotter spans
const alpha = 0.25 + normalizedIntensity * 0.35; // 0.25 to 0.6
return `rgba(255, 100, 50, ${alpha})`;
const hotColor = getComputedStyle(document.documentElement).getPropertyValue('--span-hot-base').trim();
return `rgba(${hotColor}, ${alpha})`;
} else if (intensity > 0) {
// Cold spans: very subtle gray, almost invisible
return `rgba(150, 150, 150, 0.1)`;
const coldColor = getComputedStyle(document.documentElement).getPropertyValue('--span-cold-base').trim();
return `rgba(${coldColor}, 0.1)`;
}
return 'transparent';
}
Expand Down
8 changes: 8 additions & 0 deletions Lib/profiling/sampling/_shared_assets/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@
--spec-low: #9e9e9e;
--spec-low-text: #616161;
--spec-low-bg: rgba(158, 158, 158, 0.15);

/* Heatmap span highlighting colors */
--span-hot-base: 255, 100, 50;
--span-cold-base: 150, 150, 150;
}

/* Dark theme */
Expand Down Expand Up @@ -153,6 +157,10 @@
--spec-low: #bdbdbd;
--spec-low-text: #9e9e9e;
--spec-low-bg: rgba(189, 189, 189, 0.15);

/* Heatmap span highlighting colors - dark theme */
--span-hot-base: 255, 107, 53;
--span-cold-base: 189, 189, 189;
}

/* --------------------------------------------------------------------------
Expand Down
217 changes: 90 additions & 127 deletions Lib/profiling/sampling/live_collector/collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,88 @@ def _handle_finished_input_update(self, had_input):
if self.finished and had_input and self.display is not None:
self._update_display()

def _get_visible_rows_info(self):
"""Calculate visible rows and stats list for opcode navigation."""
stats_list = self.build_stats_list()
if self.display:
height, _ = self.display.get_dimensions()
extra_header = FINISHED_BANNER_EXTRA_LINES if self.finished else 0
max_stats = max(0, height - HEADER_LINES - extra_header - FOOTER_LINES - SAFETY_MARGIN)
stats_list = stats_list[:max_stats]
visible_rows = max(1, height - 8 - 2 - 12)
else:
visible_rows = self.limit
total_rows = len(stats_list)
return stats_list, visible_rows, total_rows

def _move_selection_down(self):
"""Move selection down in opcode mode with scrolling."""
if not self.show_opcodes:
return

stats_list, visible_rows, total_rows = self._get_visible_rows_info()
if total_rows == 0:
return

# Max scroll is when last item is at bottom
max_scroll = max(0, total_rows - visible_rows)
# Current absolute position
abs_pos = self.scroll_offset + self.selected_row

# Only move if not at the last item
if abs_pos < total_rows - 1:
# Try to move selection within visible area first
if self.selected_row < visible_rows - 1:
self.selected_row += 1
elif self.scroll_offset < max_scroll:
# Scroll down
self.scroll_offset += 1

# Clamp to valid range
self.scroll_offset = min(self.scroll_offset, max_scroll)
max_selected = min(visible_rows - 1, total_rows - self.scroll_offset - 1)
self.selected_row = min(self.selected_row, max(0, max_selected))

def _move_selection_up(self):
"""Move selection up in opcode mode with scrolling."""
if not self.show_opcodes:
return

if self.selected_row > 0:
self.selected_row -= 1
elif self.scroll_offset > 0:
self.scroll_offset -= 1

# Clamp to valid range based on actual stats_list
stats_list, visible_rows, total_rows = self._get_visible_rows_info()
if total_rows > 0:
max_scroll = max(0, total_rows - visible_rows)
self.scroll_offset = min(self.scroll_offset, max_scroll)
max_selected = min(visible_rows - 1, total_rows - self.scroll_offset - 1)
self.selected_row = min(self.selected_row, max(0, max_selected))

def _navigate_to_previous_thread(self):
"""Navigate to previous thread in PER_THREAD mode, or switch from ALL to PER_THREAD."""
if len(self.thread_ids) > 0:
if self.view_mode == "ALL":
self.view_mode = "PER_THREAD"
self.current_thread_index = len(self.thread_ids) - 1
else:
self.current_thread_index = (
self.current_thread_index - 1
) % len(self.thread_ids)

def _navigate_to_next_thread(self):
"""Navigate to next thread in PER_THREAD mode, or switch from ALL to PER_THREAD."""
if len(self.thread_ids) > 0:
if self.view_mode == "ALL":
self.view_mode = "PER_THREAD"
self.current_thread_index = 0
else:
self.current_thread_index = (
self.current_thread_index + 1
) % len(self.thread_ids)

def _show_terminal_too_small(self, height, width):
"""Display a message when terminal is too small."""
A_BOLD = self.display.get_attr("A_BOLD")
Expand Down Expand Up @@ -930,154 +1012,35 @@ def _handle_input(self):

elif ch == ord("j") or ch == ord("J"):
# Move selection down in opcode mode (with scrolling)
if self.show_opcodes:
# Use the actual displayed stats_list count, not raw result_source
# This matches what _prepare_display_data() produces
stats_list = self.build_stats_list()
if self.display:
height, _ = self.display.get_dimensions()
# Same calculation as _prepare_display_data
extra_header = FINISHED_BANNER_EXTRA_LINES if self.finished else 0
max_stats = max(0, height - HEADER_LINES - extra_header - FOOTER_LINES - SAFETY_MARGIN)
stats_list = stats_list[:max_stats]
visible_rows = max(1, height - 8 - 2 - 12)
else:
visible_rows = self.limit
total_rows = len(stats_list)
if total_rows == 0:
return
# Max scroll is when last item is at bottom
max_scroll = max(0, total_rows - visible_rows)
# Current absolute position
abs_pos = self.scroll_offset + self.selected_row
# Only move if not at the last item
if abs_pos < total_rows - 1:
# Try to move selection within visible area first
if self.selected_row < visible_rows - 1:
self.selected_row += 1
elif self.scroll_offset < max_scroll:
# Scroll down
self.scroll_offset += 1
# Clamp to valid range
self.scroll_offset = min(self.scroll_offset, max_scroll)
max_selected = min(visible_rows - 1, total_rows - self.scroll_offset - 1)
self.selected_row = min(self.selected_row, max(0, max_selected))
self._move_selection_down()

elif ch == ord("k") or ch == ord("K"):
# Move selection up in opcode mode (with scrolling)
if self.show_opcodes:
if self.selected_row > 0:
self.selected_row -= 1
elif self.scroll_offset > 0:
self.scroll_offset -= 1
# Clamp to valid range based on actual stats_list
stats_list = self.build_stats_list()
if self.display:
height, _ = self.display.get_dimensions()
extra_header = FINISHED_BANNER_EXTRA_LINES if self.finished else 0
max_stats = max(0, height - HEADER_LINES - extra_header - FOOTER_LINES - SAFETY_MARGIN)
stats_list = stats_list[:max_stats]
visible_rows = max(1, height - 8 - 2 - 12)
else:
visible_rows = self.limit
total_rows = len(stats_list)
if total_rows > 0:
max_scroll = max(0, total_rows - visible_rows)
self.scroll_offset = min(self.scroll_offset, max_scroll)
max_selected = min(visible_rows - 1, total_rows - self.scroll_offset - 1)
self.selected_row = min(self.selected_row, max(0, max_selected))
self._move_selection_up()

elif ch == curses.KEY_UP:
# Move selection up (same as 'k') when in opcode mode
if self.show_opcodes:
if self.selected_row > 0:
self.selected_row -= 1
elif self.scroll_offset > 0:
self.scroll_offset -= 1
# Clamp to valid range based on actual stats_list
stats_list = self.build_stats_list()
if self.display:
height, _ = self.display.get_dimensions()
extra_header = FINISHED_BANNER_EXTRA_LINES if self.finished else 0
max_stats = max(0, height - HEADER_LINES - extra_header - FOOTER_LINES - SAFETY_MARGIN)
stats_list = stats_list[:max_stats]
visible_rows = max(1, height - 8 - 2 - 12)
else:
visible_rows = self.limit
total_rows = len(stats_list)
if total_rows > 0:
max_scroll = max(0, total_rows - visible_rows)
self.scroll_offset = min(self.scroll_offset, max_scroll)
max_selected = min(visible_rows - 1, total_rows - self.scroll_offset - 1)
self.selected_row = min(self.selected_row, max(0, max_selected))
self._move_selection_up()
else:
# Navigate to previous thread (same as KEY_LEFT)
if len(self.thread_ids) > 0:
if self.view_mode == "ALL":
self.view_mode = "PER_THREAD"
self.current_thread_index = len(self.thread_ids) - 1
else:
self.current_thread_index = (
self.current_thread_index - 1
) % len(self.thread_ids)
self._navigate_to_previous_thread()

elif ch == curses.KEY_DOWN:
# Move selection down (same as 'j') when in opcode mode
if self.show_opcodes:
stats_list = self.build_stats_list()
if self.display:
height, _ = self.display.get_dimensions()
extra_header = FINISHED_BANNER_EXTRA_LINES if self.finished else 0
max_stats = max(0, height - HEADER_LINES - extra_header - FOOTER_LINES - SAFETY_MARGIN)
stats_list = stats_list[:max_stats]
visible_rows = max(1, height - 8 - 2 - 12)
else:
visible_rows = self.limit
total_rows = len(stats_list)
if total_rows == 0:
return
max_scroll = max(0, total_rows - visible_rows)
abs_pos = self.scroll_offset + self.selected_row
if abs_pos < total_rows - 1:
if self.selected_row < visible_rows - 1:
self.selected_row += 1
elif self.scroll_offset < max_scroll:
self.scroll_offset += 1
self.scroll_offset = min(self.scroll_offset, max_scroll)
max_selected = min(visible_rows - 1, total_rows - self.scroll_offset - 1)
self.selected_row = min(self.selected_row, max(0, max_selected))
self._move_selection_down()
else:
# Navigate to next thread (same as KEY_RIGHT)
if len(self.thread_ids) > 0:
if self.view_mode == "ALL":
self.view_mode = "PER_THREAD"
self.current_thread_index = 0
else:
self.current_thread_index = (
self.current_thread_index + 1
) % len(self.thread_ids)
self._navigate_to_next_thread()

elif ch == curses.KEY_LEFT:
# Navigate to previous thread
if len(self.thread_ids) > 0:
if self.view_mode == "ALL":
self.view_mode = "PER_THREAD"
self.current_thread_index = len(self.thread_ids) - 1
else:
self.current_thread_index = (
self.current_thread_index - 1
) % len(self.thread_ids)
self._navigate_to_previous_thread()

elif ch == curses.KEY_RIGHT:
# Navigate to next thread
if len(self.thread_ids) > 0:
if self.view_mode == "ALL":
self.view_mode = "PER_THREAD"
self.current_thread_index = 0
else:
self.current_thread_index = (
self.current_thread_index + 1
) % len(self.thread_ids)
self._navigate_to_next_thread()

# Update display if input was processed while finished
self._handle_finished_input_update(ch != -1)
Expand Down
Loading